diff --git a/docker/nginx-https.conf b/docker/nginx-https.conf index 5c48ac194..391ac0766 100644 --- a/docker/nginx-https.conf +++ b/docker/nginx-https.conf @@ -291,33 +291,6 @@ http { proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; } - # 2.8 URLs that something outside Termix still calls: the Vault OIDC - # callback, the Termix ID resolver and automation webhooks. The - # backend redirects each to the plugin that serves it now (see - # contributes.http in each plugin's manifest). New plugins never add - # a block here. - location = /vault/oidc/callback { - proxy_pass http://127.0.0.1:30001; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; - } - - location ^~ /termix-id/u/ { - proxy_pass http://127.0.0.1:30001; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location ^~ /automations/webhook/ { - rewrite ^/automations/webhook/(.*)$ /plugin-api/automations/webhook/$1 last; - } - # Every plugin's HTTP routes. One block, no per-plugin entries: a # plugin serves at /plugin-api// on the main backend and never # gets a port or a block of its own. Buffering is off so SSE streams diff --git a/docker/nginx.conf b/docker/nginx.conf index f628a5442..57c2bf752 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -260,33 +260,6 @@ http { proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; } - # 2.8 URLs that something outside Termix still calls: the Vault OIDC - # callback, the Termix ID resolver and automation webhooks. The - # backend redirects each to the plugin that serves it now (see - # contributes.http in each plugin's manifest). New plugins never add - # a block here. - location = /vault/oidc/callback { - proxy_pass http://127.0.0.1:30001; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; - } - - location ^~ /termix-id/u/ { - proxy_pass http://127.0.0.1:30001; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location ^~ /automations/webhook/ { - rewrite ^/automations/webhook/(.*)$ /plugin-api/automations/webhook/$1 last; - } - # Every plugin's HTTP routes. One block, no per-plugin entries: a # plugin serves at /plugin-api// on the main backend and never # gets a port or a block of its own. Buffering is off so SSE streams diff --git a/drizzle/mysql/0066_retire_2_8_columns.sql b/drizzle/mysql/0066_retire_2_8_columns.sql new file mode 100644 index 000000000..29454b4f3 --- /dev/null +++ b/drizzle/mysql/0066_retire_2_8_columns.sql @@ -0,0 +1,28 @@ +ALTER TABLE `ssh_folders` DROP FOREIGN KEY `ssh_folders_credential_id_ssh_credentials_id_fk`; +--> statement-breakpoint +ALTER TABLE `ssh_data` DROP COLUMN `terminal_config`;--> statement-breakpoint +ALTER TABLE `ssh_data` DROP COLUMN `quick_actions`;--> statement-breakpoint +ALTER TABLE `sessions` DROP COLUMN `oidc_sub`;--> statement-breakpoint +ALTER TABLE `sessions` DROP COLUMN `oidc_sid`;--> statement-breakpoint +ALTER TABLE `sessions` DROP COLUMN `sso_provider_id`;--> statement-breakpoint +ALTER TABLE `ssh_folders` DROP COLUMN `credential_id`;--> statement-breakpoint +ALTER TABLE `user_preferences` DROP COLUMN `command_autocomplete`;--> statement-breakpoint +ALTER TABLE `user_preferences` DROP COLUMN `confirm_snippet_execution`;--> statement-breakpoint +ALTER TABLE `user_preferences` DROP COLUMN `custom_themes`;--> statement-breakpoint +ALTER TABLE `user_preferences` DROP COLUMN `terminal_defaults`;--> statement-breakpoint +ALTER TABLE `user_preferences` DROP COLUMN `terminal_macros`;--> statement-breakpoint +ALTER TABLE `users` DROP COLUMN `is_oidc`;--> statement-breakpoint +ALTER TABLE `users` DROP COLUMN `oidc_identifier`;--> statement-breakpoint +ALTER TABLE `users` DROP COLUMN `sso_provider_id`;--> statement-breakpoint +ALTER TABLE `users` DROP COLUMN `client_id`;--> statement-breakpoint +ALTER TABLE `users` DROP COLUMN `client_secret`;--> statement-breakpoint +ALTER TABLE `users` DROP COLUMN `issuer_url`;--> statement-breakpoint +ALTER TABLE `users` DROP COLUMN `authorization_url`;--> statement-breakpoint +ALTER TABLE `users` DROP COLUMN `token_url`;--> statement-breakpoint +ALTER TABLE `users` DROP COLUMN `identifier_path`;--> statement-breakpoint +ALTER TABLE `users` DROP COLUMN `name_path`;--> statement-breakpoint +ALTER TABLE `users` DROP COLUMN `scopes`;--> statement-breakpoint +DELETE FROM `settings` WHERE `key` IN ('host_defaults', 'analytics_enabled', 'analytics_instance_id');--> statement-breakpoint +UPDATE `ssh_data` SET `username` = REPLACE(`username`, '$oidc.preferred_username', '$external.username') WHERE `username` LIKE '%$oidc.preferred_username%';--> statement-breakpoint +UPDATE `ssh_credentials` SET `username` = REPLACE(`username`, '$oidc.preferred_username', '$external.username') WHERE `username` LIKE '%$oidc.preferred_username%';--> statement-breakpoint +UPDATE `host_defaults` SET `value` = REPLACE(`value`, '$oidc.preferred_username', '$external.username') WHERE `value` LIKE '%$oidc.preferred_username%'; \ No newline at end of file diff --git a/drizzle/mysql/0067_external_session_ref.sql b/drizzle/mysql/0067_external_session_ref.sql new file mode 100644 index 000000000..f8b8402a8 --- /dev/null +++ b/drizzle/mysql/0067_external_session_ref.sql @@ -0,0 +1 @@ +ALTER TABLE `sessions` ADD `external_session_ref` text; \ No newline at end of file diff --git a/drizzle/mysql/meta/0066_snapshot.json b/drizzle/mysql/meta/0066_snapshot.json new file mode 100644 index 000000000..146cbe3da --- /dev/null +++ b/drizzle/mysql/meta/0066_snapshot.json @@ -0,0 +1,4731 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "2fdeddc6-f532-43a0-8dfd-51791cc3cf46", + "prevId": "d8cbd86b-0ab0-46ac-bab2-b47ddfa2a4fb", + "tables": { + "api_keys": { + "name": "api_keys", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_prefix": { + "name": "token_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": { + "idx_api_keys_user_id": { + "name": "idx_api_keys_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "api_keys_user_id_users_id_fk": { + "name": "api_keys_user_id_users_id_fk", + "tableFrom": "api_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "api_keys_id": { + "name": "api_keys_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "audit_logs": { + "name": "audit_logs", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_type": { + "name": "resource_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resource_name": { + "name": "resource_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "success": { + "name": "success", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_audit_logs_timestamp": { + "name": "idx_audit_logs_timestamp", + "columns": [ + "timestamp" + ], + "isUnique": false + }, + "idx_audit_logs_user_ts": { + "name": "idx_audit_logs_user_ts", + "columns": [ + "user_id", + "timestamp" + ], + "isUnique": false + }, + "idx_audit_logs_action_ts": { + "name": "idx_audit_logs_action_ts", + "columns": [ + "action", + "timestamp" + ], + "isUnique": false + }, + "idx_audit_logs_resource_ts": { + "name": "idx_audit_logs_resource_ts", + "columns": [ + "resource_type", + "timestamp" + ], + "isUnique": false + } + }, + "foreignKeys": { + "audit_logs_user_id_users_id_fk": { + "name": "audit_logs_user_id_users_id_fk", + "tableFrom": "audit_logs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "audit_logs_id": { + "name": "audit_logs_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "credential_access": { + "name": "credential_access", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('use')" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_credential_access_user_id": { + "name": "idx_credential_access_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_credential_access_role_id": { + "name": "idx_credential_access_role_id", + "columns": [ + "role_id" + ], + "isUnique": false + }, + "idx_credential_access_credential_id": { + "name": "idx_credential_access_credential_id", + "columns": [ + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "credential_access_credential_id_ssh_credentials_id_fk": { + "name": "credential_access_credential_id_ssh_credentials_id_fk", + "tableFrom": "credential_access", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_user_id_users_id_fk": { + "name": "credential_access_user_id_users_id_fk", + "tableFrom": "credential_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_role_id_roles_id_fk": { + "name": "credential_access_role_id_roles_id_fk", + "tableFrom": "credential_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_granted_by_users_id_fk": { + "name": "credential_access_granted_by_users_id_fk", + "tableFrom": "credential_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "credential_access_id": { + "name": "credential_access_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "credential_sidebar_preferences": { + "name": "credential_sidebar_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "credential_sidebar_preferences_user_id_users_id_fk": { + "name": "credential_sidebar_preferences_user_id_users_id_fk", + "tableFrom": "credential_sidebar_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "credential_sidebar_preferences_user_id": { + "name": "credential_sidebar_preferences_user_id", + "columns": [ + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "folder_access": { + "name": "folder_access", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('connect')" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_folder_access_owner_folder": { + "name": "idx_folder_access_owner_folder", + "columns": [ + "owner_user_id", + "folder" + ], + "isUnique": false + } + }, + "foreignKeys": { + "folder_access_owner_user_id_users_id_fk": { + "name": "folder_access_owner_user_id_users_id_fk", + "tableFrom": "folder_access", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_access_user_id_users_id_fk": { + "name": "folder_access_user_id_users_id_fk", + "tableFrom": "folder_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_access_role_id_roles_id_fk": { + "name": "folder_access_role_id_roles_id_fk", + "tableFrom": "folder_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_access_granted_by_users_id_fk": { + "name": "folder_access_granted_by_users_id_fk", + "tableFrom": "folder_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "folder_access_id": { + "name": "folder_access_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "host_access": { + "name": "host_access", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('connect')" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "last_accessed_at": { + "name": "last_accessed_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_count": { + "name": "access_count", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "idx_host_access_user_id": { + "name": "idx_host_access_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_host_access_role_id": { + "name": "idx_host_access_role_id", + "columns": [ + "role_id" + ], + "isUnique": false + }, + "idx_host_access_host_id": { + "name": "idx_host_access_host_id", + "columns": [ + "host_id" + ], + "isUnique": false + }, + "idx_host_access_expires_at": { + "name": "idx_host_access_expires_at", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "host_access_host_id_ssh_data_id_fk": { + "name": "host_access_host_id_ssh_data_id_fk", + "tableFrom": "host_access", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_access_user_id_users_id_fk": { + "name": "host_access_user_id_users_id_fk", + "tableFrom": "host_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_access_role_id_roles_id_fk": { + "name": "host_access_role_id_roles_id_fk", + "tableFrom": "host_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_access_granted_by_users_id_fk": { + "name": "host_access_granted_by_users_id_fk", + "tableFrom": "host_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "host_access_id": { + "name": "host_access_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "host_defaults": { + "name": "host_defaults", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "level": { + "name": "level", + "type": "enum('admin','user','folder')", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope_key": { + "name": "scope_key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folder_id": { + "name": "folder_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "namespace": { + "name": "namespace", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_host_defaults_scope_key": { + "name": "idx_host_defaults_scope_key", + "columns": [ + "scope_key", + "namespace", + "key" + ], + "isUnique": true + }, + "idx_host_defaults_user": { + "name": "idx_host_defaults_user", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_host_defaults_folder": { + "name": "idx_host_defaults_folder", + "columns": [ + "folder_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "host_defaults_user_id_users_id_fk": { + "name": "host_defaults_user_id_users_id_fk", + "tableFrom": "host_defaults", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_defaults_folder_id_ssh_folders_id_fk": { + "name": "host_defaults_folder_id_ssh_folders_id_fk", + "tableFrom": "host_defaults", + "tableTo": "ssh_folders", + "columnsFrom": [ + "folder_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "host_defaults_id": { + "name": "host_defaults_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "host_protocol_auth": { + "name": "host_protocol_auth", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('direct')" + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fields": { + "name": "fields", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "secret_fields": { + "name": "secret_fields", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_host_protocol_auth_host_protocol": { + "name": "idx_host_protocol_auth_host_protocol", + "columns": [ + "host_id", + "protocol" + ], + "isUnique": true + }, + "idx_host_protocol_auth_user": { + "name": "idx_host_protocol_auth_user", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_host_protocol_auth_credential": { + "name": "idx_host_protocol_auth_credential", + "columns": [ + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "host_protocol_auth_host_id_ssh_data_id_fk": { + "name": "host_protocol_auth_host_id_ssh_data_id_fk", + "tableFrom": "host_protocol_auth", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_protocol_auth_user_id_users_id_fk": { + "name": "host_protocol_auth_user_id_users_id_fk", + "tableFrom": "host_protocol_auth", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_protocol_auth_credential_id_ssh_credentials_id_fk": { + "name": "host_protocol_auth_credential_id_ssh_credentials_id_fk", + "tableFrom": "host_protocol_auth", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "host_protocol_auth_id": { + "name": "host_protocol_auth_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "host_sidebar_preferences": { + "name": "host_sidebar_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "host_sidebar_preferences_user_id_users_id_fk": { + "name": "host_sidebar_preferences_user_id_users_id_fk", + "tableFrom": "host_sidebar_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "host_sidebar_preferences_user_id": { + "name": "host_sidebar_preferences_user_id", + "columns": [ + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ssh_data": { + "name": "ssh_data", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "connection_type": { + "name": "connection_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('ssh')" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "port": { + "name": "port", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parent_host_id": { + "name": "parent_host_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pin": { + "name": "pin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "share_ssh_auth": { + "name": "share_ssh_auth", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "force_keyboard_interactive": { + "name": "force_keyboard_interactive", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_password": { + "name": "key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sudo_password": { + "name": "sudo_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "override_credential_username": { + "name": "override_credential_username", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "jump_hosts": { + "name": "jump_hosts", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_check_enabled": { + "name": "status_check_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "status_check_interval": { + "name": "status_check_interval", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ssh_options": { + "name": "ssh_options", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enable_ssh": { + "name": "enable_ssh", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "ssh_port": { + "name": "ssh_port", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 22 + }, + "use_socks5": { + "name": "use_socks5", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_host": { + "name": "socks5_host", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_port": { + "name": "socks5_port", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_username": { + "name": "socks5_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_password": { + "name": "socks5_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_proxy_chain": { + "name": "socks5_proxy_chain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "connection_origin": { + "name": "connection_origin", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "port_knock_sequence": { + "name": "port_knock_sequence", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_fingerprint": { + "name": "host_key_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_type": { + "name": "host_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_algorithm": { + "name": "host_key_algorithm", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "('sha256')" + }, + "host_key_first_seen": { + "name": "host_key_first_seen", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_last_verified": { + "name": "host_key_last_verified", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_changed_count": { + "name": "host_key_changed_count", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "local_only": { + "name": "local_only", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "shared_source": { + "name": "shared_source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_overrides": { + "name": "default_overrides", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_ssh_data_user_id": { + "name": "idx_ssh_data_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_ssh_data_parent_host": { + "name": "idx_ssh_data_parent_host", + "columns": [ + "parent_host_id" + ], + "isUnique": false + }, + "idx_ssh_data_credential": { + "name": "idx_ssh_data_credential", + "columns": [ + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_data_user_id_users_id_fk": { + "name": "ssh_data_user_id_users_id_fk", + "tableFrom": "ssh_data", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_data_parent_host_id_ssh_data_id_fk": { + "name": "ssh_data_parent_host_id_ssh_data_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_data", + "columnsFrom": [ + "parent_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ssh_data_id": { + "name": "ssh_data_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "ssh_data_sync_id_unique": { + "name": "ssh_data_sync_id_unique", + "columns": [ + "sync_id" + ] + } + }, + "checkConstraint": {} + }, + "plugin_install_counts": { + "name": "plugin_install_counts", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "registry_id": { + "name": "registry_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "count": { + "name": "count", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('aggregate-telemetry')" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_plugin_install_counts_plugin_registry": { + "name": "idx_plugin_install_counts_plugin_registry", + "columns": [ + "plugin_id", + "registry_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "plugin_install_counts_id": { + "name": "plugin_install_counts_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugin_migrations": { + "name": "plugin_migrations", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "migration_id": { + "name": "migration_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_plugin_migrations_plugin_migration": { + "name": "idx_plugin_migrations_plugin_migration", + "columns": [ + "plugin_id", + "migration_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_migrations_plugin_id_plugins_id_fk": { + "name": "plugin_migrations_plugin_id_plugins_id_fk", + "tableFrom": "plugin_migrations", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "plugin_migrations_id": { + "name": "plugin_migrations_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugin_permission_grants": { + "name": "plugin_permission_grants", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "capability": { + "name": "capability", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('admin')" + } + }, + "indexes": { + "idx_plugin_permission_grants_plugin_capability": { + "name": "idx_plugin_permission_grants_plugin_capability", + "columns": [ + "plugin_id", + "capability" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_permission_grants_plugin_id_plugins_id_fk": { + "name": "plugin_permission_grants_plugin_id_plugins_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_permission_grants_granted_by_users_id_fk": { + "name": "plugin_permission_grants_granted_by_users_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "plugin_permission_grants_id": { + "name": "plugin_permission_grants_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugin_registries": { + "name": "plugin_registries", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('community')" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "signing_key": { + "name": "signing_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_checked_at": { + "name": "last_checked_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_index_hash": { + "name": "last_index_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "plugin_registries_id": { + "name": "plugin_registries_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugin_settings": { + "name": "plugin_settings", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "enum('admin','user','host','secret')", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope_id": { + "name": "scope_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted": { + "name": "encrypted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_plugin_settings_scope_key": { + "name": "idx_plugin_settings_scope_key", + "columns": [ + "plugin_id", + "scope", + "scope_id", + "key" + ], + "isUnique": true + }, + "idx_plugin_settings_plugin_scope": { + "name": "idx_plugin_settings_plugin_scope", + "columns": [ + "plugin_id", + "scope" + ], + "isUnique": false + } + }, + "foreignKeys": { + "plugin_settings_plugin_id_plugins_id_fk": { + "name": "plugin_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "plugin_settings_id": { + "name": "plugin_settings_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugin_storage": { + "name": "plugin_storage", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_plugin_storage_plugin_key": { + "name": "idx_plugin_storage_plugin_key", + "columns": [ + "plugin_id", + "key" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_storage_plugin_id_plugins_id_fk": { + "name": "plugin_storage_plugin_id_plugins_id_fk", + "tableFrom": "plugin_storage", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "plugin_storage_id": { + "name": "plugin_storage_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugins": { + "name": "plugins", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('available')" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('community')" + }, + "registry_id": { + "name": "registry_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('disabled')" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "installed_at": { + "name": "installed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "auto_update": { + "name": "auto_update", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "manifest_json": { + "name": "manifest_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_plugins_registry_id": { + "name": "idx_plugins_registry_id", + "columns": [ + "registry_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "plugins_id": { + "name": "plugins_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "rbac_applied_defaults": { + "name": "rbac_applied_defaults", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "role_name": { + "name": "role_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_rbac_applied_defaults_role_permission": { + "name": "idx_rbac_applied_defaults_role_permission", + "columns": [ + "role_name", + "permission" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "rbac_applied_defaults_id": { + "name": "rbac_applied_defaults_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "rbac_known_permissions": { + "name": "rbac_known_permissions", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "permission": { + "name": "permission", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_rbac_known_permissions_permission": { + "name": "idx_rbac_known_permissions_permission", + "columns": [ + "permission" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "rbac_known_permissions_id": { + "name": "rbac_known_permissions_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "recent_activity": { + "name": "recent_activity", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_name": { + "name": "host_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_recent_activity_user_ts": { + "name": "idx_recent_activity_user_ts", + "columns": [ + "user_id", + "timestamp" + ], + "isUnique": false + } + }, + "foreignKeys": { + "recent_activity_user_id_users_id_fk": { + "name": "recent_activity_user_id_users_id_fk", + "tableFrom": "recent_activity", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "recent_activity_host_id_ssh_data_id_fk": { + "name": "recent_activity_host_id_ssh_data_id_fk", + "tableFrom": "recent_activity", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "recent_activity_id": { + "name": "recent_activity_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "roles": { + "name": "roles", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_system": { + "name": "is_system", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "roles_id": { + "name": "roles_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "roles_name_unique": { + "name": "roles_name_unique", + "columns": [ + "name" + ] + } + }, + "checkConstraint": {} + }, + "sessions": { + "name": "sessions", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "jwt_token": { + "name": "jwt_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_info": { + "name": "device_info", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_sessions_user_id": { + "name": "idx_sessions_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_sessions_expires_at": { + "name": "idx_sessions_expires_at", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "sessions_id": { + "name": "sessions_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "settings": { + "name": "settings", + "columns": { + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "settings_key": { + "name": "settings_key", + "columns": [ + "key" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "shared_credential_secrets": { + "name": "shared_credential_secrets", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "credential_access_id": { + "name": "credential_access_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_user_id": { + "name": "target_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('password')" + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_shared_credential_secrets_scope": { + "name": "idx_shared_credential_secrets_scope", + "columns": [ + "credential_access_id", + "target_user_id" + ], + "isUnique": true + }, + "idx_shared_credential_secrets_target": { + "name": "idx_shared_credential_secrets_target", + "columns": [ + "target_user_id", + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "shared_credential_secrets_target_user_id_users_id_fk": { + "name": "shared_credential_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_credential_secrets_credential_id_ssh_credentials_id_fk": { + "name": "shared_credential_secrets_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_cred_secrets_access_id_fk": { + "name": "shared_cred_secrets_access_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "credential_access", + "columnsFrom": [ + "credential_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "shared_credential_secrets_id": { + "name": "shared_credential_secrets_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "shared_host_auth_overrides": { + "name": "shared_host_auth_overrides", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ssh'" + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "shared_host_auth_overrides_host_user_protocol_unique": { + "name": "shared_host_auth_overrides_host_user_protocol_unique", + "columns": [ + "host_id", + "user_id", + "protocol" + ], + "isUnique": true + } + }, + "foreignKeys": { + "shared_host_auth_overrides_host_id_ssh_data_id_fk": { + "name": "shared_host_auth_overrides_host_id_ssh_data_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_user_id_users_id_fk": { + "name": "shared_host_auth_overrides_user_id_users_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "shared_host_auth_overrides_id": { + "name": "shared_host_auth_overrides_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "shared_host_secrets": { + "name": "shared_host_secrets", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "host_access_id": { + "name": "host_access_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_user_id": { + "name": "target_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ssh'" + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('credential')" + }, + "original_credential_id": { + "name": "original_credential_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_auth_type": { + "name": "encrypted_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_type": { + "name": "encrypted_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_domain": { + "name": "encrypted_domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_fields": { + "name": "encrypted_fields", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_shared_host_secrets_scope": { + "name": "idx_shared_host_secrets_scope", + "columns": [ + "host_access_id", + "target_user_id", + "protocol" + ], + "isUnique": true + } + }, + "foreignKeys": { + "shared_host_secrets_host_access_id_host_access_id_fk": { + "name": "shared_host_secrets_host_access_id_host_access_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "host_access", + "columnsFrom": [ + "host_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_target_user_id_users_id_fk": { + "name": "shared_host_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_original_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_secrets_original_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "original_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "shared_host_secrets_id": { + "name": "shared_host_secrets_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ssh_credential_usage": { + "name": "ssh_credential_usage", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "used_at": { + "name": "used_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_ssh_credential_usage_credential": { + "name": "idx_ssh_credential_usage_credential", + "columns": [ + "credential_id" + ], + "isUnique": false + }, + "idx_ssh_credential_usage_user": { + "name": "idx_ssh_credential_usage_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_credential_usage_credential_id_ssh_credentials_id_fk": { + "name": "ssh_credential_usage_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_host_id_ssh_data_id_fk": { + "name": "ssh_credential_usage_host_id_ssh_data_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_user_id_users_id_fk": { + "name": "ssh_credential_usage_user_id_users_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ssh_credential_usage_id": { + "name": "ssh_credential_usage_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ssh_credentials": { + "name": "ssh_credentials", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pin": { + "name": "pin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_password": { + "name": "key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "detected_key_type": { + "name": "detected_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "usage_count": { + "name": "usage_count", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "shared_source": { + "name": "shared_source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_ssh_credentials_user_id": { + "name": "idx_ssh_credentials_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_credentials_user_id_users_id_fk": { + "name": "ssh_credentials_user_id_users_id_fk", + "tableFrom": "ssh_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ssh_credentials_id": { + "name": "ssh_credentials_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "ssh_credentials_sync_id_unique": { + "name": "ssh_credentials_sync_id_unique", + "columns": [ + "sync_id" + ] + } + }, + "checkConstraint": {} + }, + "ssh_folders": { + "name": "ssh_folders", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "local_only": { + "name": "local_only", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_ssh_folders_user_id": { + "name": "idx_ssh_folders_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_folders_user_id_users_id_fk": { + "name": "ssh_folders_user_id_users_id_fk", + "tableFrom": "ssh_folders", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ssh_folders_id": { + "name": "ssh_folders_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "ssh_folders_sync_id_unique": { + "name": "ssh_folders_sync_id_unique", + "columns": [ + "sync_id" + ] + } + }, + "checkConstraint": {} + }, + "sync_conflicts": { + "name": "sync_conflicts", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "local_row": { + "name": "local_row", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "server_revision": { + "name": "server_revision", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_sync_conflicts_user": { + "name": "idx_sync_conflicts_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "sync_conflicts_user_id_users_id_fk": { + "name": "sync_conflicts_user_id_users_id_fk", + "tableFrom": "sync_conflicts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "sync_conflicts_id": { + "name": "sync_conflicts_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "sync_link": { + "name": "sync_link", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "server_url": { + "name": "server_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "server_name": { + "name": "server_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "server_version": { + "name": "server_version", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_token": { + "name": "session_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "custom_headers": { + "name": "custom_headers", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "basic_auth": { + "name": "basic_auth", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "allow_invalid_certificate": { + "name": "allow_invalid_certificate", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "remote_user_id": { + "name": "remote_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "remote_username": { + "name": "remote_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "account": { + "name": "account", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "known_types": { + "name": "known_types", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cursor": { + "name": "cursor", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('idle')" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "linked_at": { + "name": "linked_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "sync_link_user_id_users_id_fk": { + "name": "sync_link_user_id_users_id_fk", + "tableFrom": "sync_link", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "sync_link_id": { + "name": "sync_link_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "sync_records": { + "name": "sync_records", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_type": { + "name": "entity_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "revision": { + "name": "revision", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "seq": { + "name": "seq", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted": { + "name": "deleted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_hash": { + "name": "error_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_sync_records_user_entity_sync": { + "name": "idx_sync_records_user_entity_sync", + "columns": [ + "user_id", + "entity_type", + "sync_id" + ], + "isUnique": true + }, + "idx_sync_records_user_seq": { + "name": "idx_sync_records_user_seq", + "columns": [ + "user_id", + "seq" + ], + "isUnique": false + } + }, + "foreignKeys": { + "sync_records_user_id_users_id_fk": { + "name": "sync_records_user_id_users_id_fk", + "tableFrom": "sync_records", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "sync_records_id": { + "name": "sync_records_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "trusted_devices": { + "name": "trusted_devices", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_fingerprint": { + "name": "device_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_info": { + "name": "device_info", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_trusted_devices_user_id": { + "name": "idx_trusted_devices_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "trusted_devices_user_id_users_id_fk": { + "name": "trusted_devices_user_id_users_id_fk", + "tableFrom": "trusted_devices", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "trusted_devices_id": { + "name": "trusted_devices_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ui_preferences": { + "name": "ui_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "ui_preferences_user_id_users_id_fk": { + "name": "ui_preferences_user_id_users_id_fk", + "tableFrom": "ui_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ui_preferences_user_id": { + "name": "ui_preferences_user_id", + "columns": [ + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user_external_identities": { + "name": "user_external_identities", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_user_external_identities_provider_subject": { + "name": "idx_user_external_identities_provider_subject", + "columns": [ + "provider_id", + "subject" + ], + "isUnique": true + }, + "idx_user_external_identities_user": { + "name": "idx_user_external_identities_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_external_identities_user_id_users_id_fk": { + "name": "user_external_identities_user_id_users_id_fk", + "tableFrom": "user_external_identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_external_identities_id": { + "name": "user_external_identities_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user_open_tabs": { + "name": "user_open_tabs", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_type": { + "name": "tab_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_order": { + "name": "tab_order", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "backend_session_id": { + "name": "backend_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_user_open_tabs_user_id": { + "name": "idx_user_open_tabs_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_open_tabs_user_id_users_id_fk": { + "name": "user_open_tabs_user_id_users_id_fk", + "tableFrom": "user_open_tabs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_open_tabs_host_id_ssh_data_id_fk": { + "name": "user_open_tabs_host_id_ssh_data_id_fk", + "tableFrom": "user_open_tabs", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_open_tabs_id": { + "name": "user_open_tabs_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user_preferences": { + "name": "user_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reopen_tabs_on_login": { + "name": "reopen_tabs_on_login", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "font_size": { + "name": "font_size", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "accent_color": { + "name": "accent_color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "storage_mode": { + "name": "storage_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "command_palette_enabled": { + "name": "command_palette_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "show_host_tags": { + "name": "show_host_tags", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_tray_on_click": { + "name": "host_tray_on_click", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pin_app_rail": { + "name": "pin_app_rail", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expand_app_rail_on_hover": { + "name": "expand_app_rail_on_hover", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "show_pin_app_rail_button": { + "name": "show_pin_app_rail_button", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folders_collapsed": { + "name": "folders_collapsed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "disable_update_check": { + "name": "disable_update_check", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "confirm_tab_close": { + "name": "confirm_tab_close", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hidden_rail_tabs": { + "name": "hidden_rail_tabs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "compact_host_view": { + "name": "compact_host_view", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_color_scheme": { + "name": "status_color_scheme", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "custom_keybindings": { + "name": "custom_keybindings", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "user_preferences_user_id_users_id_fk": { + "name": "user_preferences_user_id_users_id_fk", + "tableFrom": "user_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_preferences_user_id": { + "name": "user_preferences_user_id", + "columns": [ + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user_roles": { + "name": "user_roles", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_user_roles_user_role": { + "name": "idx_user_roles_user_role", + "columns": [ + "user_id", + "role_id" + ], + "isUnique": true + }, + "idx_user_roles_role_id": { + "name": "idx_user_roles_role_id", + "columns": [ + "role_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_granted_by_users_id_fk": { + "name": "user_roles_granted_by_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_roles_id": { + "name": "user_roles_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user_second_factors": { + "name": "user_second_factors", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "factor_id": { + "name": "factor_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enrolled_at": { + "name": "enrolled_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_user_second_factors_user_factor": { + "name": "idx_user_second_factors_user_factor", + "columns": [ + "user_id", + "plugin_id", + "factor_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "user_second_factors_user_id_users_id_fk": { + "name": "user_second_factors_user_id_users_id_fk", + "tableFrom": "user_second_factors", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_second_factors_id": { + "name": "user_second_factors_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "registered_at": { + "name": "registered_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "donation_modal_dismissed": { + "name": "donation_modal_dismissed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "users_id": { + "name": "users_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + } + }, + "views": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "tables": {}, + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/mysql/meta/0067_snapshot.json b/drizzle/mysql/meta/0067_snapshot.json new file mode 100644 index 000000000..5d628221c --- /dev/null +++ b/drizzle/mysql/meta/0067_snapshot.json @@ -0,0 +1,4738 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "b91f5f72-d0db-46df-915c-f8bce033cd9a", + "prevId": "2fdeddc6-f532-43a0-8dfd-51791cc3cf46", + "tables": { + "api_keys": { + "name": "api_keys", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_prefix": { + "name": "token_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": { + "idx_api_keys_user_id": { + "name": "idx_api_keys_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "api_keys_user_id_users_id_fk": { + "name": "api_keys_user_id_users_id_fk", + "tableFrom": "api_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "api_keys_id": { + "name": "api_keys_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "audit_logs": { + "name": "audit_logs", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_type": { + "name": "resource_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resource_name": { + "name": "resource_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "success": { + "name": "success", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_audit_logs_timestamp": { + "name": "idx_audit_logs_timestamp", + "columns": [ + "timestamp" + ], + "isUnique": false + }, + "idx_audit_logs_user_ts": { + "name": "idx_audit_logs_user_ts", + "columns": [ + "user_id", + "timestamp" + ], + "isUnique": false + }, + "idx_audit_logs_action_ts": { + "name": "idx_audit_logs_action_ts", + "columns": [ + "action", + "timestamp" + ], + "isUnique": false + }, + "idx_audit_logs_resource_ts": { + "name": "idx_audit_logs_resource_ts", + "columns": [ + "resource_type", + "timestamp" + ], + "isUnique": false + } + }, + "foreignKeys": { + "audit_logs_user_id_users_id_fk": { + "name": "audit_logs_user_id_users_id_fk", + "tableFrom": "audit_logs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "audit_logs_id": { + "name": "audit_logs_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "credential_access": { + "name": "credential_access", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('use')" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_credential_access_user_id": { + "name": "idx_credential_access_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_credential_access_role_id": { + "name": "idx_credential_access_role_id", + "columns": [ + "role_id" + ], + "isUnique": false + }, + "idx_credential_access_credential_id": { + "name": "idx_credential_access_credential_id", + "columns": [ + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "credential_access_credential_id_ssh_credentials_id_fk": { + "name": "credential_access_credential_id_ssh_credentials_id_fk", + "tableFrom": "credential_access", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_user_id_users_id_fk": { + "name": "credential_access_user_id_users_id_fk", + "tableFrom": "credential_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_role_id_roles_id_fk": { + "name": "credential_access_role_id_roles_id_fk", + "tableFrom": "credential_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_granted_by_users_id_fk": { + "name": "credential_access_granted_by_users_id_fk", + "tableFrom": "credential_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "credential_access_id": { + "name": "credential_access_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "credential_sidebar_preferences": { + "name": "credential_sidebar_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "credential_sidebar_preferences_user_id_users_id_fk": { + "name": "credential_sidebar_preferences_user_id_users_id_fk", + "tableFrom": "credential_sidebar_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "credential_sidebar_preferences_user_id": { + "name": "credential_sidebar_preferences_user_id", + "columns": [ + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "folder_access": { + "name": "folder_access", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('connect')" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_folder_access_owner_folder": { + "name": "idx_folder_access_owner_folder", + "columns": [ + "owner_user_id", + "folder" + ], + "isUnique": false + } + }, + "foreignKeys": { + "folder_access_owner_user_id_users_id_fk": { + "name": "folder_access_owner_user_id_users_id_fk", + "tableFrom": "folder_access", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_access_user_id_users_id_fk": { + "name": "folder_access_user_id_users_id_fk", + "tableFrom": "folder_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_access_role_id_roles_id_fk": { + "name": "folder_access_role_id_roles_id_fk", + "tableFrom": "folder_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_access_granted_by_users_id_fk": { + "name": "folder_access_granted_by_users_id_fk", + "tableFrom": "folder_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "folder_access_id": { + "name": "folder_access_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "host_access": { + "name": "host_access", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('connect')" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "last_accessed_at": { + "name": "last_accessed_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_count": { + "name": "access_count", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "idx_host_access_user_id": { + "name": "idx_host_access_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_host_access_role_id": { + "name": "idx_host_access_role_id", + "columns": [ + "role_id" + ], + "isUnique": false + }, + "idx_host_access_host_id": { + "name": "idx_host_access_host_id", + "columns": [ + "host_id" + ], + "isUnique": false + }, + "idx_host_access_expires_at": { + "name": "idx_host_access_expires_at", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "host_access_host_id_ssh_data_id_fk": { + "name": "host_access_host_id_ssh_data_id_fk", + "tableFrom": "host_access", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_access_user_id_users_id_fk": { + "name": "host_access_user_id_users_id_fk", + "tableFrom": "host_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_access_role_id_roles_id_fk": { + "name": "host_access_role_id_roles_id_fk", + "tableFrom": "host_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_access_granted_by_users_id_fk": { + "name": "host_access_granted_by_users_id_fk", + "tableFrom": "host_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "host_access_id": { + "name": "host_access_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "host_defaults": { + "name": "host_defaults", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "level": { + "name": "level", + "type": "enum('admin','user','folder')", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope_key": { + "name": "scope_key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folder_id": { + "name": "folder_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "namespace": { + "name": "namespace", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_host_defaults_scope_key": { + "name": "idx_host_defaults_scope_key", + "columns": [ + "scope_key", + "namespace", + "key" + ], + "isUnique": true + }, + "idx_host_defaults_user": { + "name": "idx_host_defaults_user", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_host_defaults_folder": { + "name": "idx_host_defaults_folder", + "columns": [ + "folder_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "host_defaults_user_id_users_id_fk": { + "name": "host_defaults_user_id_users_id_fk", + "tableFrom": "host_defaults", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_defaults_folder_id_ssh_folders_id_fk": { + "name": "host_defaults_folder_id_ssh_folders_id_fk", + "tableFrom": "host_defaults", + "tableTo": "ssh_folders", + "columnsFrom": [ + "folder_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "host_defaults_id": { + "name": "host_defaults_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "host_protocol_auth": { + "name": "host_protocol_auth", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('direct')" + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fields": { + "name": "fields", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "secret_fields": { + "name": "secret_fields", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_host_protocol_auth_host_protocol": { + "name": "idx_host_protocol_auth_host_protocol", + "columns": [ + "host_id", + "protocol" + ], + "isUnique": true + }, + "idx_host_protocol_auth_user": { + "name": "idx_host_protocol_auth_user", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_host_protocol_auth_credential": { + "name": "idx_host_protocol_auth_credential", + "columns": [ + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "host_protocol_auth_host_id_ssh_data_id_fk": { + "name": "host_protocol_auth_host_id_ssh_data_id_fk", + "tableFrom": "host_protocol_auth", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_protocol_auth_user_id_users_id_fk": { + "name": "host_protocol_auth_user_id_users_id_fk", + "tableFrom": "host_protocol_auth", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_protocol_auth_credential_id_ssh_credentials_id_fk": { + "name": "host_protocol_auth_credential_id_ssh_credentials_id_fk", + "tableFrom": "host_protocol_auth", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "host_protocol_auth_id": { + "name": "host_protocol_auth_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "host_sidebar_preferences": { + "name": "host_sidebar_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "host_sidebar_preferences_user_id_users_id_fk": { + "name": "host_sidebar_preferences_user_id_users_id_fk", + "tableFrom": "host_sidebar_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "host_sidebar_preferences_user_id": { + "name": "host_sidebar_preferences_user_id", + "columns": [ + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ssh_data": { + "name": "ssh_data", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "connection_type": { + "name": "connection_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('ssh')" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "port": { + "name": "port", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parent_host_id": { + "name": "parent_host_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pin": { + "name": "pin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "share_ssh_auth": { + "name": "share_ssh_auth", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "force_keyboard_interactive": { + "name": "force_keyboard_interactive", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_password": { + "name": "key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sudo_password": { + "name": "sudo_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "override_credential_username": { + "name": "override_credential_username", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "jump_hosts": { + "name": "jump_hosts", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_check_enabled": { + "name": "status_check_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "status_check_interval": { + "name": "status_check_interval", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ssh_options": { + "name": "ssh_options", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enable_ssh": { + "name": "enable_ssh", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "ssh_port": { + "name": "ssh_port", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 22 + }, + "use_socks5": { + "name": "use_socks5", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_host": { + "name": "socks5_host", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_port": { + "name": "socks5_port", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_username": { + "name": "socks5_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_password": { + "name": "socks5_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_proxy_chain": { + "name": "socks5_proxy_chain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "connection_origin": { + "name": "connection_origin", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "port_knock_sequence": { + "name": "port_knock_sequence", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_fingerprint": { + "name": "host_key_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_type": { + "name": "host_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_algorithm": { + "name": "host_key_algorithm", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "('sha256')" + }, + "host_key_first_seen": { + "name": "host_key_first_seen", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_last_verified": { + "name": "host_key_last_verified", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_changed_count": { + "name": "host_key_changed_count", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "local_only": { + "name": "local_only", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "shared_source": { + "name": "shared_source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_overrides": { + "name": "default_overrides", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_ssh_data_user_id": { + "name": "idx_ssh_data_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_ssh_data_parent_host": { + "name": "idx_ssh_data_parent_host", + "columns": [ + "parent_host_id" + ], + "isUnique": false + }, + "idx_ssh_data_credential": { + "name": "idx_ssh_data_credential", + "columns": [ + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_data_user_id_users_id_fk": { + "name": "ssh_data_user_id_users_id_fk", + "tableFrom": "ssh_data", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_data_parent_host_id_ssh_data_id_fk": { + "name": "ssh_data_parent_host_id_ssh_data_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_data", + "columnsFrom": [ + "parent_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ssh_data_id": { + "name": "ssh_data_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "ssh_data_sync_id_unique": { + "name": "ssh_data_sync_id_unique", + "columns": [ + "sync_id" + ] + } + }, + "checkConstraint": {} + }, + "plugin_install_counts": { + "name": "plugin_install_counts", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "registry_id": { + "name": "registry_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "count": { + "name": "count", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('aggregate-telemetry')" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_plugin_install_counts_plugin_registry": { + "name": "idx_plugin_install_counts_plugin_registry", + "columns": [ + "plugin_id", + "registry_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "plugin_install_counts_id": { + "name": "plugin_install_counts_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugin_migrations": { + "name": "plugin_migrations", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "migration_id": { + "name": "migration_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_plugin_migrations_plugin_migration": { + "name": "idx_plugin_migrations_plugin_migration", + "columns": [ + "plugin_id", + "migration_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_migrations_plugin_id_plugins_id_fk": { + "name": "plugin_migrations_plugin_id_plugins_id_fk", + "tableFrom": "plugin_migrations", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "plugin_migrations_id": { + "name": "plugin_migrations_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugin_permission_grants": { + "name": "plugin_permission_grants", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "capability": { + "name": "capability", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('admin')" + } + }, + "indexes": { + "idx_plugin_permission_grants_plugin_capability": { + "name": "idx_plugin_permission_grants_plugin_capability", + "columns": [ + "plugin_id", + "capability" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_permission_grants_plugin_id_plugins_id_fk": { + "name": "plugin_permission_grants_plugin_id_plugins_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_permission_grants_granted_by_users_id_fk": { + "name": "plugin_permission_grants_granted_by_users_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "plugin_permission_grants_id": { + "name": "plugin_permission_grants_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugin_registries": { + "name": "plugin_registries", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('community')" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "signing_key": { + "name": "signing_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_checked_at": { + "name": "last_checked_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_index_hash": { + "name": "last_index_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "plugin_registries_id": { + "name": "plugin_registries_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugin_settings": { + "name": "plugin_settings", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "enum('admin','user','host','secret')", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope_id": { + "name": "scope_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted": { + "name": "encrypted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_plugin_settings_scope_key": { + "name": "idx_plugin_settings_scope_key", + "columns": [ + "plugin_id", + "scope", + "scope_id", + "key" + ], + "isUnique": true + }, + "idx_plugin_settings_plugin_scope": { + "name": "idx_plugin_settings_plugin_scope", + "columns": [ + "plugin_id", + "scope" + ], + "isUnique": false + } + }, + "foreignKeys": { + "plugin_settings_plugin_id_plugins_id_fk": { + "name": "plugin_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "plugin_settings_id": { + "name": "plugin_settings_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugin_storage": { + "name": "plugin_storage", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_plugin_storage_plugin_key": { + "name": "idx_plugin_storage_plugin_key", + "columns": [ + "plugin_id", + "key" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_storage_plugin_id_plugins_id_fk": { + "name": "plugin_storage_plugin_id_plugins_id_fk", + "tableFrom": "plugin_storage", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "plugin_storage_id": { + "name": "plugin_storage_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "plugins": { + "name": "plugins", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('available')" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('community')" + }, + "registry_id": { + "name": "registry_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('disabled')" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "installed_at": { + "name": "installed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "auto_update": { + "name": "auto_update", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "manifest_json": { + "name": "manifest_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_plugins_registry_id": { + "name": "idx_plugins_registry_id", + "columns": [ + "registry_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "plugins_id": { + "name": "plugins_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "rbac_applied_defaults": { + "name": "rbac_applied_defaults", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "role_name": { + "name": "role_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_rbac_applied_defaults_role_permission": { + "name": "idx_rbac_applied_defaults_role_permission", + "columns": [ + "role_name", + "permission" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "rbac_applied_defaults_id": { + "name": "rbac_applied_defaults_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "rbac_known_permissions": { + "name": "rbac_known_permissions", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "permission": { + "name": "permission", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_rbac_known_permissions_permission": { + "name": "idx_rbac_known_permissions_permission", + "columns": [ + "permission" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "rbac_known_permissions_id": { + "name": "rbac_known_permissions_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "recent_activity": { + "name": "recent_activity", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_name": { + "name": "host_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_recent_activity_user_ts": { + "name": "idx_recent_activity_user_ts", + "columns": [ + "user_id", + "timestamp" + ], + "isUnique": false + } + }, + "foreignKeys": { + "recent_activity_user_id_users_id_fk": { + "name": "recent_activity_user_id_users_id_fk", + "tableFrom": "recent_activity", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "recent_activity_host_id_ssh_data_id_fk": { + "name": "recent_activity_host_id_ssh_data_id_fk", + "tableFrom": "recent_activity", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "recent_activity_id": { + "name": "recent_activity_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "roles": { + "name": "roles", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_system": { + "name": "is_system", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "roles_id": { + "name": "roles_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "roles_name_unique": { + "name": "roles_name_unique", + "columns": [ + "name" + ] + } + }, + "checkConstraint": {} + }, + "sessions": { + "name": "sessions", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "jwt_token": { + "name": "jwt_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_info": { + "name": "device_info", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "external_session_ref": { + "name": "external_session_ref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_sessions_user_id": { + "name": "idx_sessions_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_sessions_expires_at": { + "name": "idx_sessions_expires_at", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "sessions_id": { + "name": "sessions_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "settings": { + "name": "settings", + "columns": { + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "settings_key": { + "name": "settings_key", + "columns": [ + "key" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "shared_credential_secrets": { + "name": "shared_credential_secrets", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "credential_access_id": { + "name": "credential_access_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_user_id": { + "name": "target_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('password')" + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_shared_credential_secrets_scope": { + "name": "idx_shared_credential_secrets_scope", + "columns": [ + "credential_access_id", + "target_user_id" + ], + "isUnique": true + }, + "idx_shared_credential_secrets_target": { + "name": "idx_shared_credential_secrets_target", + "columns": [ + "target_user_id", + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "shared_credential_secrets_target_user_id_users_id_fk": { + "name": "shared_credential_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_credential_secrets_credential_id_ssh_credentials_id_fk": { + "name": "shared_credential_secrets_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_cred_secrets_access_id_fk": { + "name": "shared_cred_secrets_access_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "credential_access", + "columnsFrom": [ + "credential_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "shared_credential_secrets_id": { + "name": "shared_credential_secrets_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "shared_host_auth_overrides": { + "name": "shared_host_auth_overrides", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ssh'" + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "shared_host_auth_overrides_host_user_protocol_unique": { + "name": "shared_host_auth_overrides_host_user_protocol_unique", + "columns": [ + "host_id", + "user_id", + "protocol" + ], + "isUnique": true + } + }, + "foreignKeys": { + "shared_host_auth_overrides_host_id_ssh_data_id_fk": { + "name": "shared_host_auth_overrides_host_id_ssh_data_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_user_id_users_id_fk": { + "name": "shared_host_auth_overrides_user_id_users_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "shared_host_auth_overrides_id": { + "name": "shared_host_auth_overrides_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "shared_host_secrets": { + "name": "shared_host_secrets", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "host_access_id": { + "name": "host_access_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_user_id": { + "name": "target_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ssh'" + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('credential')" + }, + "original_credential_id": { + "name": "original_credential_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_auth_type": { + "name": "encrypted_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_type": { + "name": "encrypted_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_domain": { + "name": "encrypted_domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_fields": { + "name": "encrypted_fields", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_shared_host_secrets_scope": { + "name": "idx_shared_host_secrets_scope", + "columns": [ + "host_access_id", + "target_user_id", + "protocol" + ], + "isUnique": true + } + }, + "foreignKeys": { + "shared_host_secrets_host_access_id_host_access_id_fk": { + "name": "shared_host_secrets_host_access_id_host_access_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "host_access", + "columnsFrom": [ + "host_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_target_user_id_users_id_fk": { + "name": "shared_host_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_original_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_secrets_original_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "original_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "shared_host_secrets_id": { + "name": "shared_host_secrets_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ssh_credential_usage": { + "name": "ssh_credential_usage", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "used_at": { + "name": "used_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_ssh_credential_usage_credential": { + "name": "idx_ssh_credential_usage_credential", + "columns": [ + "credential_id" + ], + "isUnique": false + }, + "idx_ssh_credential_usage_user": { + "name": "idx_ssh_credential_usage_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_credential_usage_credential_id_ssh_credentials_id_fk": { + "name": "ssh_credential_usage_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_host_id_ssh_data_id_fk": { + "name": "ssh_credential_usage_host_id_ssh_data_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_user_id_users_id_fk": { + "name": "ssh_credential_usage_user_id_users_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ssh_credential_usage_id": { + "name": "ssh_credential_usage_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ssh_credentials": { + "name": "ssh_credentials", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pin": { + "name": "pin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_password": { + "name": "key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "detected_key_type": { + "name": "detected_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "usage_count": { + "name": "usage_count", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "shared_source": { + "name": "shared_source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_ssh_credentials_user_id": { + "name": "idx_ssh_credentials_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_credentials_user_id_users_id_fk": { + "name": "ssh_credentials_user_id_users_id_fk", + "tableFrom": "ssh_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ssh_credentials_id": { + "name": "ssh_credentials_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "ssh_credentials_sync_id_unique": { + "name": "ssh_credentials_sync_id_unique", + "columns": [ + "sync_id" + ] + } + }, + "checkConstraint": {} + }, + "ssh_folders": { + "name": "ssh_folders", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "local_only": { + "name": "local_only", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_ssh_folders_user_id": { + "name": "idx_ssh_folders_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_folders_user_id_users_id_fk": { + "name": "ssh_folders_user_id_users_id_fk", + "tableFrom": "ssh_folders", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ssh_folders_id": { + "name": "ssh_folders_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "ssh_folders_sync_id_unique": { + "name": "ssh_folders_sync_id_unique", + "columns": [ + "sync_id" + ] + } + }, + "checkConstraint": {} + }, + "sync_conflicts": { + "name": "sync_conflicts", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "local_row": { + "name": "local_row", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "server_revision": { + "name": "server_revision", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_sync_conflicts_user": { + "name": "idx_sync_conflicts_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "sync_conflicts_user_id_users_id_fk": { + "name": "sync_conflicts_user_id_users_id_fk", + "tableFrom": "sync_conflicts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "sync_conflicts_id": { + "name": "sync_conflicts_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "sync_link": { + "name": "sync_link", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "server_url": { + "name": "server_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "server_name": { + "name": "server_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "server_version": { + "name": "server_version", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_token": { + "name": "session_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "custom_headers": { + "name": "custom_headers", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "basic_auth": { + "name": "basic_auth", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "allow_invalid_certificate": { + "name": "allow_invalid_certificate", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "remote_user_id": { + "name": "remote_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "remote_username": { + "name": "remote_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "account": { + "name": "account", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "known_types": { + "name": "known_types", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cursor": { + "name": "cursor", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('idle')" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "linked_at": { + "name": "linked_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "sync_link_user_id_users_id_fk": { + "name": "sync_link_user_id_users_id_fk", + "tableFrom": "sync_link", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "sync_link_id": { + "name": "sync_link_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "sync_records": { + "name": "sync_records", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_type": { + "name": "entity_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "revision": { + "name": "revision", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "seq": { + "name": "seq", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted": { + "name": "deleted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_hash": { + "name": "error_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_sync_records_user_entity_sync": { + "name": "idx_sync_records_user_entity_sync", + "columns": [ + "user_id", + "entity_type", + "sync_id" + ], + "isUnique": true + }, + "idx_sync_records_user_seq": { + "name": "idx_sync_records_user_seq", + "columns": [ + "user_id", + "seq" + ], + "isUnique": false + } + }, + "foreignKeys": { + "sync_records_user_id_users_id_fk": { + "name": "sync_records_user_id_users_id_fk", + "tableFrom": "sync_records", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "sync_records_id": { + "name": "sync_records_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "trusted_devices": { + "name": "trusted_devices", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_fingerprint": { + "name": "device_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_info": { + "name": "device_info", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_trusted_devices_user_id": { + "name": "idx_trusted_devices_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "trusted_devices_user_id_users_id_fk": { + "name": "trusted_devices_user_id_users_id_fk", + "tableFrom": "trusted_devices", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "trusted_devices_id": { + "name": "trusted_devices_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ui_preferences": { + "name": "ui_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "ui_preferences_user_id_users_id_fk": { + "name": "ui_preferences_user_id_users_id_fk", + "tableFrom": "ui_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ui_preferences_user_id": { + "name": "ui_preferences_user_id", + "columns": [ + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user_external_identities": { + "name": "user_external_identities", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_user_external_identities_provider_subject": { + "name": "idx_user_external_identities_provider_subject", + "columns": [ + "provider_id", + "subject" + ], + "isUnique": true + }, + "idx_user_external_identities_user": { + "name": "idx_user_external_identities_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_external_identities_user_id_users_id_fk": { + "name": "user_external_identities_user_id_users_id_fk", + "tableFrom": "user_external_identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_external_identities_id": { + "name": "user_external_identities_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user_open_tabs": { + "name": "user_open_tabs", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_type": { + "name": "tab_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_order": { + "name": "tab_order", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "backend_session_id": { + "name": "backend_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_user_open_tabs_user_id": { + "name": "idx_user_open_tabs_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_open_tabs_user_id_users_id_fk": { + "name": "user_open_tabs_user_id_users_id_fk", + "tableFrom": "user_open_tabs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_open_tabs_host_id_ssh_data_id_fk": { + "name": "user_open_tabs_host_id_ssh_data_id_fk", + "tableFrom": "user_open_tabs", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_open_tabs_id": { + "name": "user_open_tabs_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user_preferences": { + "name": "user_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reopen_tabs_on_login": { + "name": "reopen_tabs_on_login", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "font_size": { + "name": "font_size", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "accent_color": { + "name": "accent_color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "storage_mode": { + "name": "storage_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "command_palette_enabled": { + "name": "command_palette_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "show_host_tags": { + "name": "show_host_tags", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_tray_on_click": { + "name": "host_tray_on_click", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pin_app_rail": { + "name": "pin_app_rail", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expand_app_rail_on_hover": { + "name": "expand_app_rail_on_hover", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "show_pin_app_rail_button": { + "name": "show_pin_app_rail_button", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folders_collapsed": { + "name": "folders_collapsed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "disable_update_check": { + "name": "disable_update_check", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "confirm_tab_close": { + "name": "confirm_tab_close", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hidden_rail_tabs": { + "name": "hidden_rail_tabs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "compact_host_view": { + "name": "compact_host_view", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_color_scheme": { + "name": "status_color_scheme", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "custom_keybindings": { + "name": "custom_keybindings", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "user_preferences_user_id_users_id_fk": { + "name": "user_preferences_user_id_users_id_fk", + "tableFrom": "user_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_preferences_user_id": { + "name": "user_preferences_user_id", + "columns": [ + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user_roles": { + "name": "user_roles", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_user_roles_user_role": { + "name": "idx_user_roles_user_role", + "columns": [ + "user_id", + "role_id" + ], + "isUnique": true + }, + "idx_user_roles_role_id": { + "name": "idx_user_roles_role_id", + "columns": [ + "role_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_granted_by_users_id_fk": { + "name": "user_roles_granted_by_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_roles_id": { + "name": "user_roles_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user_second_factors": { + "name": "user_second_factors", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "factor_id": { + "name": "factor_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enrolled_at": { + "name": "enrolled_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_user_second_factors_user_factor": { + "name": "idx_user_second_factors_user_factor", + "columns": [ + "user_id", + "plugin_id", + "factor_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "user_second_factors_user_id_users_id_fk": { + "name": "user_second_factors_user_id_users_id_fk", + "tableFrom": "user_second_factors", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_second_factors_id": { + "name": "user_second_factors_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "registered_at": { + "name": "registered_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "donation_modal_dismissed": { + "name": "donation_modal_dismissed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "users_id": { + "name": "users_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + } + }, + "views": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "tables": {}, + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/mysql/meta/_journal.json b/drizzle/mysql/meta/_journal.json index ebf03aab8..797c7c578 100644 --- a/drizzle/mysql/meta/_journal.json +++ b/drizzle/mysql/meta/_journal.json @@ -463,6 +463,20 @@ "when": 1790742086934, "tag": "0065_ambiguous_agent_zero", "breakpoints": true + }, + { + "idx": 66, + "version": "5", + "when": 1791209297104, + "tag": "0066_retire_2_8_columns", + "breakpoints": true + }, + { + "idx": 67, + "version": "5", + "when": 1791209311841, + "tag": "0067_external_session_ref", + "breakpoints": true } ] } \ No newline at end of file diff --git a/drizzle/postgres/0066_retire_2_8_columns.sql b/drizzle/postgres/0066_retire_2_8_columns.sql new file mode 100644 index 000000000..068f9b61c --- /dev/null +++ b/drizzle/postgres/0066_retire_2_8_columns.sql @@ -0,0 +1,28 @@ +ALTER TABLE "ssh_folders" DROP CONSTRAINT "ssh_folders_credential_id_ssh_credentials_id_fk"; +--> statement-breakpoint +ALTER TABLE "ssh_data" DROP COLUMN "terminal_config";--> statement-breakpoint +ALTER TABLE "ssh_data" DROP COLUMN "quick_actions";--> statement-breakpoint +ALTER TABLE "sessions" DROP COLUMN "oidc_sub";--> statement-breakpoint +ALTER TABLE "sessions" DROP COLUMN "oidc_sid";--> statement-breakpoint +ALTER TABLE "sessions" DROP COLUMN "sso_provider_id";--> statement-breakpoint +ALTER TABLE "ssh_folders" DROP COLUMN "credential_id";--> statement-breakpoint +ALTER TABLE "user_preferences" DROP COLUMN "command_autocomplete";--> statement-breakpoint +ALTER TABLE "user_preferences" DROP COLUMN "confirm_snippet_execution";--> statement-breakpoint +ALTER TABLE "user_preferences" DROP COLUMN "custom_themes";--> statement-breakpoint +ALTER TABLE "user_preferences" DROP COLUMN "terminal_defaults";--> statement-breakpoint +ALTER TABLE "user_preferences" DROP COLUMN "terminal_macros";--> statement-breakpoint +ALTER TABLE "users" DROP COLUMN "is_oidc";--> statement-breakpoint +ALTER TABLE "users" DROP COLUMN "oidc_identifier";--> statement-breakpoint +ALTER TABLE "users" DROP COLUMN "sso_provider_id";--> statement-breakpoint +ALTER TABLE "users" DROP COLUMN "client_id";--> statement-breakpoint +ALTER TABLE "users" DROP COLUMN "client_secret";--> statement-breakpoint +ALTER TABLE "users" DROP COLUMN "issuer_url";--> statement-breakpoint +ALTER TABLE "users" DROP COLUMN "authorization_url";--> statement-breakpoint +ALTER TABLE "users" DROP COLUMN "token_url";--> statement-breakpoint +ALTER TABLE "users" DROP COLUMN "identifier_path";--> statement-breakpoint +ALTER TABLE "users" DROP COLUMN "name_path";--> statement-breakpoint +ALTER TABLE "users" DROP COLUMN "scopes";--> statement-breakpoint +DELETE FROM "settings" WHERE "key" IN ('host_defaults', 'analytics_enabled', 'analytics_instance_id');--> statement-breakpoint +UPDATE "ssh_data" SET "username" = REPLACE("username", '$oidc.preferred_username', '$external.username') WHERE "username" LIKE '%$oidc.preferred_username%';--> statement-breakpoint +UPDATE "ssh_credentials" SET "username" = REPLACE("username", '$oidc.preferred_username', '$external.username') WHERE "username" LIKE '%$oidc.preferred_username%';--> statement-breakpoint +UPDATE "host_defaults" SET "value" = REPLACE("value", '$oidc.preferred_username', '$external.username') WHERE "value" LIKE '%$oidc.preferred_username%'; \ No newline at end of file diff --git a/drizzle/postgres/0067_external_session_ref.sql b/drizzle/postgres/0067_external_session_ref.sql new file mode 100644 index 000000000..896b7b1c3 --- /dev/null +++ b/drizzle/postgres/0067_external_session_ref.sql @@ -0,0 +1 @@ +ALTER TABLE "sessions" ADD COLUMN "external_session_ref" text; \ No newline at end of file diff --git a/drizzle/postgres/meta/0066_snapshot.json b/drizzle/postgres/meta/0066_snapshot.json new file mode 100644 index 000000000..bd12fc436 --- /dev/null +++ b/drizzle/postgres/meta/0066_snapshot.json @@ -0,0 +1,4758 @@ +{ + "id": "63dca749-99fe-403c-8a34-84c4ac5cdab3", + "prevId": "772f1576-e033-4d2b-8e40-c7b3d499058e", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.api_keys": { + "name": "api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_prefix": { + "name": "token_prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": { + "idx_api_keys_user_id": { + "name": "idx_api_keys_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "api_keys_user_id_users_id_fk": { + "name": "api_keys_user_id_users_id_fk", + "tableFrom": "api_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_logs": { + "name": "audit_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resource_name": { + "name": "resource_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "success": { + "name": "success", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timestamp": { + "name": "timestamp", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_audit_logs_timestamp": { + "name": "idx_audit_logs_timestamp", + "columns": [ + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_logs_user_ts": { + "name": "idx_audit_logs_user_ts", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_logs_action_ts": { + "name": "idx_audit_logs_action_ts", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_logs_resource_ts": { + "name": "idx_audit_logs_resource_ts", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audit_logs_user_id_users_id_fk": { + "name": "audit_logs_user_id_users_id_fk", + "tableFrom": "audit_logs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential_access": { + "name": "credential_access", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'use'" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_credential_access_user_id": { + "name": "idx_credential_access_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_credential_access_role_id": { + "name": "idx_credential_access_role_id", + "columns": [ + { + "expression": "role_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_credential_access_credential_id": { + "name": "idx_credential_access_credential_id", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_access_credential_id_ssh_credentials_id_fk": { + "name": "credential_access_credential_id_ssh_credentials_id_fk", + "tableFrom": "credential_access", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_user_id_users_id_fk": { + "name": "credential_access_user_id_users_id_fk", + "tableFrom": "credential_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_role_id_roles_id_fk": { + "name": "credential_access_role_id_roles_id_fk", + "tableFrom": "credential_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_granted_by_users_id_fk": { + "name": "credential_access_granted_by_users_id_fk", + "tableFrom": "credential_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential_sidebar_preferences": { + "name": "credential_sidebar_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "credential_sidebar_preferences_user_id_users_id_fk": { + "name": "credential_sidebar_preferences_user_id_users_id_fk", + "tableFrom": "credential_sidebar_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.folder_access": { + "name": "folder_access", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'connect'" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_folder_access_owner_folder": { + "name": "idx_folder_access_owner_folder", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "folder", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "folder_access_owner_user_id_users_id_fk": { + "name": "folder_access_owner_user_id_users_id_fk", + "tableFrom": "folder_access", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_access_user_id_users_id_fk": { + "name": "folder_access_user_id_users_id_fk", + "tableFrom": "folder_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_access_role_id_roles_id_fk": { + "name": "folder_access_role_id_roles_id_fk", + "tableFrom": "folder_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_access_granted_by_users_id_fk": { + "name": "folder_access_granted_by_users_id_fk", + "tableFrom": "folder_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.host_access": { + "name": "host_access", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'connect'" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "last_accessed_at": { + "name": "last_accessed_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_count": { + "name": "access_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "idx_host_access_user_id": { + "name": "idx_host_access_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_host_access_role_id": { + "name": "idx_host_access_role_id", + "columns": [ + { + "expression": "role_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_host_access_host_id": { + "name": "idx_host_access_host_id", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_host_access_expires_at": { + "name": "idx_host_access_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "host_access_host_id_ssh_data_id_fk": { + "name": "host_access_host_id_ssh_data_id_fk", + "tableFrom": "host_access", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_access_user_id_users_id_fk": { + "name": "host_access_user_id_users_id_fk", + "tableFrom": "host_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_access_role_id_roles_id_fk": { + "name": "host_access_role_id_roles_id_fk", + "tableFrom": "host_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_access_granted_by_users_id_fk": { + "name": "host_access_granted_by_users_id_fk", + "tableFrom": "host_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.host_defaults": { + "name": "host_defaults", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_key": { + "name": "scope_key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "namespace": { + "name": "namespace", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_host_defaults_scope_key": { + "name": "idx_host_defaults_scope_key", + "columns": [ + { + "expression": "scope_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "namespace", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_host_defaults_user": { + "name": "idx_host_defaults_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_host_defaults_folder": { + "name": "idx_host_defaults_folder", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "host_defaults_user_id_users_id_fk": { + "name": "host_defaults_user_id_users_id_fk", + "tableFrom": "host_defaults", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_defaults_folder_id_ssh_folders_id_fk": { + "name": "host_defaults_folder_id_ssh_folders_id_fk", + "tableFrom": "host_defaults", + "tableTo": "ssh_folders", + "columnsFrom": [ + "folder_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.host_protocol_auth": { + "name": "host_protocol_auth", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'direct'" + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fields": { + "name": "fields", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_fields": { + "name": "secret_fields", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_host_protocol_auth_host_protocol": { + "name": "idx_host_protocol_auth_host_protocol", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "protocol", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_host_protocol_auth_user": { + "name": "idx_host_protocol_auth_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_host_protocol_auth_credential": { + "name": "idx_host_protocol_auth_credential", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "host_protocol_auth_host_id_ssh_data_id_fk": { + "name": "host_protocol_auth_host_id_ssh_data_id_fk", + "tableFrom": "host_protocol_auth", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_protocol_auth_user_id_users_id_fk": { + "name": "host_protocol_auth_user_id_users_id_fk", + "tableFrom": "host_protocol_auth", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_protocol_auth_credential_id_ssh_credentials_id_fk": { + "name": "host_protocol_auth_credential_id_ssh_credentials_id_fk", + "tableFrom": "host_protocol_auth", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.host_sidebar_preferences": { + "name": "host_sidebar_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "host_sidebar_preferences_user_id_users_id_fk": { + "name": "host_sidebar_preferences_user_id_users_id_fk", + "tableFrom": "host_sidebar_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ssh_data": { + "name": "ssh_data", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "connection_type": { + "name": "connection_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ssh'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "folder": { + "name": "folder", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parent_host_id": { + "name": "parent_host_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pin": { + "name": "pin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "share_ssh_auth": { + "name": "share_ssh_auth", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "force_keyboard_interactive": { + "name": "force_keyboard_interactive", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_password": { + "name": "key_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sudo_password": { + "name": "sudo_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "override_credential_username": { + "name": "override_credential_username", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "jump_hosts": { + "name": "jump_hosts", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_check_enabled": { + "name": "status_check_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "status_check_interval": { + "name": "status_check_interval", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "ssh_options": { + "name": "ssh_options", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enable_ssh": { + "name": "enable_ssh", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "ssh_port": { + "name": "ssh_port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 22 + }, + "use_socks5": { + "name": "use_socks5", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "socks5_host": { + "name": "socks5_host", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "socks5_port": { + "name": "socks5_port", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "socks5_username": { + "name": "socks5_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "socks5_password": { + "name": "socks5_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "socks5_proxy_chain": { + "name": "socks5_proxy_chain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connection_origin": { + "name": "connection_origin", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "port_knock_sequence": { + "name": "port_knock_sequence", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "host_key_fingerprint": { + "name": "host_key_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "host_key_type": { + "name": "host_key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "host_key_algorithm": { + "name": "host_key_algorithm", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'sha256'" + }, + "host_key_first_seen": { + "name": "host_key_first_seen", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "host_key_last_verified": { + "name": "host_key_last_verified", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "host_key_changed_count": { + "name": "host_key_changed_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "local_only": { + "name": "local_only", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "shared_source": { + "name": "shared_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_overrides": { + "name": "default_overrides", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ssh_data_user_id": { + "name": "idx_ssh_data_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ssh_data_parent_host": { + "name": "idx_ssh_data_parent_host", + "columns": [ + { + "expression": "parent_host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ssh_data_credential": { + "name": "idx_ssh_data_credential", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ssh_data_user_id_users_id_fk": { + "name": "ssh_data_user_id_users_id_fk", + "tableFrom": "ssh_data", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_data_parent_host_id_ssh_data_id_fk": { + "name": "ssh_data_parent_host_id_ssh_data_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_data", + "columnsFrom": [ + "parent_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ssh_data_sync_id_unique": { + "name": "ssh_data_sync_id_unique", + "nullsNotDistinct": false, + "columns": [ + "sync_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_install_counts": { + "name": "plugin_install_counts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "registry_id": { + "name": "registry_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'aggregate-telemetry'" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_install_counts_plugin_registry": { + "name": "idx_plugin_install_counts_plugin_registry", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "registry_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_migrations": { + "name": "plugin_migrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "migration_id": { + "name": "migration_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_migrations_plugin_migration": { + "name": "idx_plugin_migrations_plugin_migration", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "migration_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_migrations_plugin_id_plugins_id_fk": { + "name": "plugin_migrations_plugin_id_plugins_id_fk", + "tableFrom": "plugin_migrations", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_permission_grants": { + "name": "plugin_permission_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "capability": { + "name": "capability", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'admin'" + } + }, + "indexes": { + "idx_plugin_permission_grants_plugin_capability": { + "name": "idx_plugin_permission_grants_plugin_capability", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "capability", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_permission_grants_plugin_id_plugins_id_fk": { + "name": "plugin_permission_grants_plugin_id_plugins_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_permission_grants_granted_by_users_id_fk": { + "name": "plugin_permission_grants_granted_by_users_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_registries": { + "name": "plugin_registries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'community'" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "signing_key": { + "name": "signing_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_checked_at": { + "name": "last_checked_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_index_hash": { + "name": "last_index_hash", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_settings": { + "name": "plugin_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted": { + "name": "encrypted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_settings_scope_key": { + "name": "idx_plugin_settings_scope_key", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_plugin_settings_plugin_scope": { + "name": "idx_plugin_settings_plugin_scope", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_settings_plugin_id_plugins_id_fk": { + "name": "plugin_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_storage": { + "name": "plugin_storage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_storage_plugin_key": { + "name": "idx_plugin_storage_plugin_key", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_storage_plugin_id_plugins_id_fk": { + "name": "plugin_storage_plugin_id_plugins_id_fk", + "tableFrom": "plugin_storage", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugins": { + "name": "plugins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'available'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'community'" + }, + "registry_id": { + "name": "registry_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'disabled'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_at": { + "name": "installed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "auto_update": { + "name": "auto_update", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "manifest_json": { + "name": "manifest_json", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_plugins_registry_id": { + "name": "idx_plugins_registry_id", + "columns": [ + { + "expression": "registry_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rbac_applied_defaults": { + "name": "rbac_applied_defaults", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "role_name": { + "name": "role_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "permission": { + "name": "permission", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_rbac_applied_defaults_role_permission": { + "name": "idx_rbac_applied_defaults_role_permission", + "columns": [ + { + "expression": "role_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rbac_known_permissions": { + "name": "rbac_known_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "permission": { + "name": "permission", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_rbac_known_permissions_permission": { + "name": "idx_rbac_known_permissions_permission", + "columns": [ + { + "expression": "permission", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.recent_activity": { + "name": "recent_activity", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "host_name": { + "name": "host_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timestamp": { + "name": "timestamp", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_recent_activity_user_ts": { + "name": "idx_recent_activity_user_ts", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "recent_activity_user_id_users_id_fk": { + "name": "recent_activity_user_id_users_id_fk", + "tableFrom": "recent_activity", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "recent_activity_host_id_ssh_data_id_fk": { + "name": "recent_activity_host_id_ssh_data_id_fk", + "tableFrom": "recent_activity", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.roles": { + "name": "roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_system": { + "name": "is_system", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "roles_name_unique": { + "name": "roles_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "jwt_token": { + "name": "jwt_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_info": { + "name": "device_info", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "last_active_at": { + "name": "last_active_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_sessions_user_id": { + "name": "idx_sessions_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_sessions_expires_at": { + "name": "idx_sessions_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.settings": { + "name": "settings", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shared_credential_secrets": { + "name": "shared_credential_secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "credential_access_id": { + "name": "credential_access_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "target_user_id": { + "name": "target_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'password'" + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_shared_credential_secrets_scope": { + "name": "idx_shared_credential_secrets_scope", + "columns": [ + { + "expression": "credential_access_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_shared_credential_secrets_target": { + "name": "idx_shared_credential_secrets_target", + "columns": [ + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "shared_credential_secrets_target_user_id_users_id_fk": { + "name": "shared_credential_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_credential_secrets_credential_id_ssh_credentials_id_fk": { + "name": "shared_credential_secrets_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_cred_secrets_access_id_fk": { + "name": "shared_cred_secrets_access_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "credential_access", + "columnsFrom": [ + "credential_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shared_host_auth_overrides": { + "name": "shared_host_auth_overrides", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'ssh'" + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "shared_host_auth_overrides_host_user_protocol_unique": { + "name": "shared_host_auth_overrides_host_user_protocol_unique", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "protocol", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "shared_host_auth_overrides_host_id_ssh_data_id_fk": { + "name": "shared_host_auth_overrides_host_id_ssh_data_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_user_id_users_id_fk": { + "name": "shared_host_auth_overrides_user_id_users_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shared_host_secrets": { + "name": "shared_host_secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "host_access_id": { + "name": "host_access_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "target_user_id": { + "name": "target_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'ssh'" + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'credential'" + }, + "original_credential_id": { + "name": "original_credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_auth_type": { + "name": "encrypted_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_key_type": { + "name": "encrypted_key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_domain": { + "name": "encrypted_domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_fields": { + "name": "encrypted_fields", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_shared_host_secrets_scope": { + "name": "idx_shared_host_secrets_scope", + "columns": [ + { + "expression": "host_access_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "protocol", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "shared_host_secrets_host_access_id_host_access_id_fk": { + "name": "shared_host_secrets_host_access_id_host_access_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "host_access", + "columnsFrom": [ + "host_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_target_user_id_users_id_fk": { + "name": "shared_host_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_original_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_secrets_original_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "original_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ssh_credential_usage": { + "name": "ssh_credential_usage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ssh_credential_usage_credential": { + "name": "idx_ssh_credential_usage_credential", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ssh_credential_usage_user": { + "name": "idx_ssh_credential_usage_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ssh_credential_usage_credential_id_ssh_credentials_id_fk": { + "name": "ssh_credential_usage_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_host_id_ssh_data_id_fk": { + "name": "ssh_credential_usage_host_id_ssh_data_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_user_id_users_id_fk": { + "name": "ssh_credential_usage_user_id_users_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ssh_credentials": { + "name": "ssh_credentials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder": { + "name": "folder", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pin": { + "name": "pin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_password": { + "name": "key_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "detected_key_type": { + "name": "detected_key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "usage_count": { + "name": "usage_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "shared_source": { + "name": "shared_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ssh_credentials_user_id": { + "name": "idx_ssh_credentials_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ssh_credentials_user_id_users_id_fk": { + "name": "ssh_credentials_user_id_users_id_fk", + "tableFrom": "ssh_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ssh_credentials_sync_id_unique": { + "name": "ssh_credentials_sync_id_unique", + "nullsNotDistinct": false, + "columns": [ + "sync_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ssh_folders": { + "name": "ssh_folders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "local_only": { + "name": "local_only", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ssh_folders_user_id": { + "name": "idx_ssh_folders_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ssh_folders_user_id_users_id_fk": { + "name": "ssh_folders_user_id_users_id_fk", + "tableFrom": "ssh_folders", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ssh_folders_sync_id_unique": { + "name": "ssh_folders_sync_id_unique", + "nullsNotDistinct": false, + "columns": [ + "sync_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sync_conflicts": { + "name": "sync_conflicts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "local_row": { + "name": "local_row", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "server_revision": { + "name": "server_revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_sync_conflicts_user": { + "name": "idx_sync_conflicts_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sync_conflicts_user_id_users_id_fk": { + "name": "sync_conflicts_user_id_users_id_fk", + "tableFrom": "sync_conflicts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sync_link": { + "name": "sync_link", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "server_url": { + "name": "server_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "server_name": { + "name": "server_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "server_version": { + "name": "server_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_token": { + "name": "session_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "custom_headers": { + "name": "custom_headers", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "basic_auth": { + "name": "basic_auth", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allow_invalid_certificate": { + "name": "allow_invalid_certificate", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "remote_user_id": { + "name": "remote_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_username": { + "name": "remote_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "account": { + "name": "account", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "known_types": { + "name": "known_types", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cursor": { + "name": "cursor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "linked_at": { + "name": "linked_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sync_link_user_id_users_id_fk": { + "name": "sync_link_user_id_users_id_fk", + "tableFrom": "sync_link", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sync_records": { + "name": "sync_records", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted": { + "name": "deleted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_hash": { + "name": "error_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_sync_records_user_entity_sync": { + "name": "idx_sync_records_user_entity_sync", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sync_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_sync_records_user_seq": { + "name": "idx_sync_records_user_seq", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sync_records_user_id_users_id_fk": { + "name": "sync_records_user_id_users_id_fk", + "tableFrom": "sync_records", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.trusted_devices": { + "name": "trusted_devices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "device_fingerprint": { + "name": "device_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_info": { + "name": "device_info", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_trusted_devices_user_id": { + "name": "idx_trusted_devices_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "trusted_devices_user_id_users_id_fk": { + "name": "trusted_devices_user_id_users_id_fk", + "tableFrom": "trusted_devices", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ui_preferences": { + "name": "ui_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "ui_preferences_user_id_users_id_fk": { + "name": "ui_preferences_user_id_users_id_fk", + "tableFrom": "ui_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_external_identities": { + "name": "user_external_identities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_external_identities_provider_subject": { + "name": "idx_user_external_identities_provider_subject", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_user_external_identities_user": { + "name": "idx_user_external_identities_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_external_identities_user_id_users_id_fk": { + "name": "user_external_identities_user_id_users_id_fk", + "tableFrom": "user_external_identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_open_tabs": { + "name": "user_open_tabs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "tab_type": { + "name": "tab_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tab_order": { + "name": "tab_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "backend_session_id": { + "name": "backend_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_open_tabs_user_id": { + "name": "idx_user_open_tabs_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_open_tabs_user_id_users_id_fk": { + "name": "user_open_tabs_user_id_users_id_fk", + "tableFrom": "user_open_tabs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_open_tabs_host_id_ssh_data_id_fk": { + "name": "user_open_tabs_host_id_ssh_data_id_fk", + "tableFrom": "user_open_tabs", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_preferences": { + "name": "user_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "reopen_tabs_on_login": { + "name": "reopen_tabs_on_login", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "font_size": { + "name": "font_size", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accent_color": { + "name": "accent_color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "storage_mode": { + "name": "storage_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command_palette_enabled": { + "name": "command_palette_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "show_host_tags": { + "name": "show_host_tags", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "host_tray_on_click": { + "name": "host_tray_on_click", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "pin_app_rail": { + "name": "pin_app_rail", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "expand_app_rail_on_hover": { + "name": "expand_app_rail_on_hover", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "show_pin_app_rail_button": { + "name": "show_pin_app_rail_button", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "folders_collapsed": { + "name": "folders_collapsed", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "disable_update_check": { + "name": "disable_update_check", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "confirm_tab_close": { + "name": "confirm_tab_close", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "hidden_rail_tabs": { + "name": "hidden_rail_tabs", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "compact_host_view": { + "name": "compact_host_view", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "status_color_scheme": { + "name": "status_color_scheme", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "custom_keybindings": { + "name": "custom_keybindings", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "user_preferences_user_id_users_id_fk": { + "name": "user_preferences_user_id_users_id_fk", + "tableFrom": "user_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_roles": { + "name": "user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_roles_user_role": { + "name": "idx_user_roles_user_role", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_user_roles_role_id": { + "name": "idx_user_roles_role_id", + "columns": [ + { + "expression": "role_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_granted_by_users_id_fk": { + "name": "user_roles_granted_by_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_second_factors": { + "name": "user_second_factors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "factor_id": { + "name": "factor_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "enrolled_at": { + "name": "enrolled_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_second_factors_user_factor": { + "name": "idx_user_second_factors_user_factor", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "factor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_second_factors_user_id_users_id_fk": { + "name": "user_second_factors_user_id_users_id_fk", + "tableFrom": "user_second_factors", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "registered_at": { + "name": "registered_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "donation_modal_dismissed": { + "name": "donation_modal_dismissed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/postgres/meta/0067_snapshot.json b/drizzle/postgres/meta/0067_snapshot.json new file mode 100644 index 000000000..c0aa8dcd6 --- /dev/null +++ b/drizzle/postgres/meta/0067_snapshot.json @@ -0,0 +1,4764 @@ +{ + "id": "0e009e23-d22a-4b65-a142-0887e2f243a3", + "prevId": "63dca749-99fe-403c-8a34-84c4ac5cdab3", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.api_keys": { + "name": "api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_prefix": { + "name": "token_prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": { + "idx_api_keys_user_id": { + "name": "idx_api_keys_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "api_keys_user_id_users_id_fk": { + "name": "api_keys_user_id_users_id_fk", + "tableFrom": "api_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_logs": { + "name": "audit_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resource_name": { + "name": "resource_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "success": { + "name": "success", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timestamp": { + "name": "timestamp", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_audit_logs_timestamp": { + "name": "idx_audit_logs_timestamp", + "columns": [ + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_logs_user_ts": { + "name": "idx_audit_logs_user_ts", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_logs_action_ts": { + "name": "idx_audit_logs_action_ts", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_logs_resource_ts": { + "name": "idx_audit_logs_resource_ts", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audit_logs_user_id_users_id_fk": { + "name": "audit_logs_user_id_users_id_fk", + "tableFrom": "audit_logs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential_access": { + "name": "credential_access", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'use'" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_credential_access_user_id": { + "name": "idx_credential_access_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_credential_access_role_id": { + "name": "idx_credential_access_role_id", + "columns": [ + { + "expression": "role_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_credential_access_credential_id": { + "name": "idx_credential_access_credential_id", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_access_credential_id_ssh_credentials_id_fk": { + "name": "credential_access_credential_id_ssh_credentials_id_fk", + "tableFrom": "credential_access", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_user_id_users_id_fk": { + "name": "credential_access_user_id_users_id_fk", + "tableFrom": "credential_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_role_id_roles_id_fk": { + "name": "credential_access_role_id_roles_id_fk", + "tableFrom": "credential_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_granted_by_users_id_fk": { + "name": "credential_access_granted_by_users_id_fk", + "tableFrom": "credential_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential_sidebar_preferences": { + "name": "credential_sidebar_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "credential_sidebar_preferences_user_id_users_id_fk": { + "name": "credential_sidebar_preferences_user_id_users_id_fk", + "tableFrom": "credential_sidebar_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.folder_access": { + "name": "folder_access", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'connect'" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_folder_access_owner_folder": { + "name": "idx_folder_access_owner_folder", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "folder", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "folder_access_owner_user_id_users_id_fk": { + "name": "folder_access_owner_user_id_users_id_fk", + "tableFrom": "folder_access", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_access_user_id_users_id_fk": { + "name": "folder_access_user_id_users_id_fk", + "tableFrom": "folder_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_access_role_id_roles_id_fk": { + "name": "folder_access_role_id_roles_id_fk", + "tableFrom": "folder_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_access_granted_by_users_id_fk": { + "name": "folder_access_granted_by_users_id_fk", + "tableFrom": "folder_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.host_access": { + "name": "host_access", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'connect'" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "last_accessed_at": { + "name": "last_accessed_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_count": { + "name": "access_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "idx_host_access_user_id": { + "name": "idx_host_access_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_host_access_role_id": { + "name": "idx_host_access_role_id", + "columns": [ + { + "expression": "role_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_host_access_host_id": { + "name": "idx_host_access_host_id", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_host_access_expires_at": { + "name": "idx_host_access_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "host_access_host_id_ssh_data_id_fk": { + "name": "host_access_host_id_ssh_data_id_fk", + "tableFrom": "host_access", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_access_user_id_users_id_fk": { + "name": "host_access_user_id_users_id_fk", + "tableFrom": "host_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_access_role_id_roles_id_fk": { + "name": "host_access_role_id_roles_id_fk", + "tableFrom": "host_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_access_granted_by_users_id_fk": { + "name": "host_access_granted_by_users_id_fk", + "tableFrom": "host_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.host_defaults": { + "name": "host_defaults", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_key": { + "name": "scope_key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "namespace": { + "name": "namespace", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_host_defaults_scope_key": { + "name": "idx_host_defaults_scope_key", + "columns": [ + { + "expression": "scope_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "namespace", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_host_defaults_user": { + "name": "idx_host_defaults_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_host_defaults_folder": { + "name": "idx_host_defaults_folder", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "host_defaults_user_id_users_id_fk": { + "name": "host_defaults_user_id_users_id_fk", + "tableFrom": "host_defaults", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_defaults_folder_id_ssh_folders_id_fk": { + "name": "host_defaults_folder_id_ssh_folders_id_fk", + "tableFrom": "host_defaults", + "tableTo": "ssh_folders", + "columnsFrom": [ + "folder_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.host_protocol_auth": { + "name": "host_protocol_auth", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'direct'" + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fields": { + "name": "fields", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_fields": { + "name": "secret_fields", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_host_protocol_auth_host_protocol": { + "name": "idx_host_protocol_auth_host_protocol", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "protocol", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_host_protocol_auth_user": { + "name": "idx_host_protocol_auth_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_host_protocol_auth_credential": { + "name": "idx_host_protocol_auth_credential", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "host_protocol_auth_host_id_ssh_data_id_fk": { + "name": "host_protocol_auth_host_id_ssh_data_id_fk", + "tableFrom": "host_protocol_auth", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_protocol_auth_user_id_users_id_fk": { + "name": "host_protocol_auth_user_id_users_id_fk", + "tableFrom": "host_protocol_auth", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_protocol_auth_credential_id_ssh_credentials_id_fk": { + "name": "host_protocol_auth_credential_id_ssh_credentials_id_fk", + "tableFrom": "host_protocol_auth", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.host_sidebar_preferences": { + "name": "host_sidebar_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "host_sidebar_preferences_user_id_users_id_fk": { + "name": "host_sidebar_preferences_user_id_users_id_fk", + "tableFrom": "host_sidebar_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ssh_data": { + "name": "ssh_data", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "connection_type": { + "name": "connection_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ssh'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "folder": { + "name": "folder", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parent_host_id": { + "name": "parent_host_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pin": { + "name": "pin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "share_ssh_auth": { + "name": "share_ssh_auth", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "force_keyboard_interactive": { + "name": "force_keyboard_interactive", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_password": { + "name": "key_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sudo_password": { + "name": "sudo_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "override_credential_username": { + "name": "override_credential_username", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "jump_hosts": { + "name": "jump_hosts", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_check_enabled": { + "name": "status_check_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "status_check_interval": { + "name": "status_check_interval", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "ssh_options": { + "name": "ssh_options", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enable_ssh": { + "name": "enable_ssh", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "ssh_port": { + "name": "ssh_port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 22 + }, + "use_socks5": { + "name": "use_socks5", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "socks5_host": { + "name": "socks5_host", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "socks5_port": { + "name": "socks5_port", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "socks5_username": { + "name": "socks5_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "socks5_password": { + "name": "socks5_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "socks5_proxy_chain": { + "name": "socks5_proxy_chain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connection_origin": { + "name": "connection_origin", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "port_knock_sequence": { + "name": "port_knock_sequence", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "host_key_fingerprint": { + "name": "host_key_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "host_key_type": { + "name": "host_key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "host_key_algorithm": { + "name": "host_key_algorithm", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'sha256'" + }, + "host_key_first_seen": { + "name": "host_key_first_seen", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "host_key_last_verified": { + "name": "host_key_last_verified", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "host_key_changed_count": { + "name": "host_key_changed_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "local_only": { + "name": "local_only", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "shared_source": { + "name": "shared_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_overrides": { + "name": "default_overrides", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ssh_data_user_id": { + "name": "idx_ssh_data_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ssh_data_parent_host": { + "name": "idx_ssh_data_parent_host", + "columns": [ + { + "expression": "parent_host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ssh_data_credential": { + "name": "idx_ssh_data_credential", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ssh_data_user_id_users_id_fk": { + "name": "ssh_data_user_id_users_id_fk", + "tableFrom": "ssh_data", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_data_parent_host_id_ssh_data_id_fk": { + "name": "ssh_data_parent_host_id_ssh_data_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_data", + "columnsFrom": [ + "parent_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ssh_data_sync_id_unique": { + "name": "ssh_data_sync_id_unique", + "nullsNotDistinct": false, + "columns": [ + "sync_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_install_counts": { + "name": "plugin_install_counts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "registry_id": { + "name": "registry_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'aggregate-telemetry'" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_install_counts_plugin_registry": { + "name": "idx_plugin_install_counts_plugin_registry", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "registry_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_migrations": { + "name": "plugin_migrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "migration_id": { + "name": "migration_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_migrations_plugin_migration": { + "name": "idx_plugin_migrations_plugin_migration", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "migration_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_migrations_plugin_id_plugins_id_fk": { + "name": "plugin_migrations_plugin_id_plugins_id_fk", + "tableFrom": "plugin_migrations", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_permission_grants": { + "name": "plugin_permission_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "capability": { + "name": "capability", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'admin'" + } + }, + "indexes": { + "idx_plugin_permission_grants_plugin_capability": { + "name": "idx_plugin_permission_grants_plugin_capability", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "capability", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_permission_grants_plugin_id_plugins_id_fk": { + "name": "plugin_permission_grants_plugin_id_plugins_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_permission_grants_granted_by_users_id_fk": { + "name": "plugin_permission_grants_granted_by_users_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_registries": { + "name": "plugin_registries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'community'" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "signing_key": { + "name": "signing_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_checked_at": { + "name": "last_checked_at", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_index_hash": { + "name": "last_index_hash", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_settings": { + "name": "plugin_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted": { + "name": "encrypted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_settings_scope_key": { + "name": "idx_plugin_settings_scope_key", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_plugin_settings_plugin_scope": { + "name": "idx_plugin_settings_plugin_scope", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_settings_plugin_id_plugins_id_fk": { + "name": "plugin_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_storage": { + "name": "plugin_storage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_storage_plugin_key": { + "name": "idx_plugin_storage_plugin_key", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_storage_plugin_id_plugins_id_fk": { + "name": "plugin_storage_plugin_id_plugins_id_fk", + "tableFrom": "plugin_storage", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugins": { + "name": "plugins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'available'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'community'" + }, + "registry_id": { + "name": "registry_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'disabled'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_at": { + "name": "installed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "auto_update": { + "name": "auto_update", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "manifest_json": { + "name": "manifest_json", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_plugins_registry_id": { + "name": "idx_plugins_registry_id", + "columns": [ + { + "expression": "registry_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rbac_applied_defaults": { + "name": "rbac_applied_defaults", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "role_name": { + "name": "role_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "permission": { + "name": "permission", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_rbac_applied_defaults_role_permission": { + "name": "idx_rbac_applied_defaults_role_permission", + "columns": [ + { + "expression": "role_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rbac_known_permissions": { + "name": "rbac_known_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "permission": { + "name": "permission", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_rbac_known_permissions_permission": { + "name": "idx_rbac_known_permissions_permission", + "columns": [ + { + "expression": "permission", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.recent_activity": { + "name": "recent_activity", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "host_name": { + "name": "host_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timestamp": { + "name": "timestamp", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_recent_activity_user_ts": { + "name": "idx_recent_activity_user_ts", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "recent_activity_user_id_users_id_fk": { + "name": "recent_activity_user_id_users_id_fk", + "tableFrom": "recent_activity", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "recent_activity_host_id_ssh_data_id_fk": { + "name": "recent_activity_host_id_ssh_data_id_fk", + "tableFrom": "recent_activity", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.roles": { + "name": "roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_system": { + "name": "is_system", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "roles_name_unique": { + "name": "roles_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "jwt_token": { + "name": "jwt_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_info": { + "name": "device_info", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_session_ref": { + "name": "external_session_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "last_active_at": { + "name": "last_active_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_sessions_user_id": { + "name": "idx_sessions_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_sessions_expires_at": { + "name": "idx_sessions_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.settings": { + "name": "settings", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shared_credential_secrets": { + "name": "shared_credential_secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "credential_access_id": { + "name": "credential_access_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "target_user_id": { + "name": "target_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'password'" + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_shared_credential_secrets_scope": { + "name": "idx_shared_credential_secrets_scope", + "columns": [ + { + "expression": "credential_access_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_shared_credential_secrets_target": { + "name": "idx_shared_credential_secrets_target", + "columns": [ + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "shared_credential_secrets_target_user_id_users_id_fk": { + "name": "shared_credential_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_credential_secrets_credential_id_ssh_credentials_id_fk": { + "name": "shared_credential_secrets_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_cred_secrets_access_id_fk": { + "name": "shared_cred_secrets_access_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "credential_access", + "columnsFrom": [ + "credential_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shared_host_auth_overrides": { + "name": "shared_host_auth_overrides", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'ssh'" + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "shared_host_auth_overrides_host_user_protocol_unique": { + "name": "shared_host_auth_overrides_host_user_protocol_unique", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "protocol", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "shared_host_auth_overrides_host_id_ssh_data_id_fk": { + "name": "shared_host_auth_overrides_host_id_ssh_data_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_user_id_users_id_fk": { + "name": "shared_host_auth_overrides_user_id_users_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shared_host_secrets": { + "name": "shared_host_secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "host_access_id": { + "name": "host_access_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "target_user_id": { + "name": "target_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "protocol": { + "name": "protocol", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'ssh'" + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'credential'" + }, + "original_credential_id": { + "name": "original_credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_auth_type": { + "name": "encrypted_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_key_type": { + "name": "encrypted_key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_domain": { + "name": "encrypted_domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_fields": { + "name": "encrypted_fields", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_shared_host_secrets_scope": { + "name": "idx_shared_host_secrets_scope", + "columns": [ + { + "expression": "host_access_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "protocol", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "shared_host_secrets_host_access_id_host_access_id_fk": { + "name": "shared_host_secrets_host_access_id_host_access_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "host_access", + "columnsFrom": [ + "host_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_target_user_id_users_id_fk": { + "name": "shared_host_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_original_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_secrets_original_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "original_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ssh_credential_usage": { + "name": "ssh_credential_usage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ssh_credential_usage_credential": { + "name": "idx_ssh_credential_usage_credential", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ssh_credential_usage_user": { + "name": "idx_ssh_credential_usage_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ssh_credential_usage_credential_id_ssh_credentials_id_fk": { + "name": "ssh_credential_usage_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_host_id_ssh_data_id_fk": { + "name": "ssh_credential_usage_host_id_ssh_data_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_user_id_users_id_fk": { + "name": "ssh_credential_usage_user_id_users_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ssh_credentials": { + "name": "ssh_credentials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder": { + "name": "folder", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pin": { + "name": "pin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_password": { + "name": "key_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "detected_key_type": { + "name": "detected_key_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "usage_count": { + "name": "usage_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "shared_source": { + "name": "shared_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ssh_credentials_user_id": { + "name": "idx_ssh_credentials_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ssh_credentials_user_id_users_id_fk": { + "name": "ssh_credentials_user_id_users_id_fk", + "tableFrom": "ssh_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ssh_credentials_sync_id_unique": { + "name": "ssh_credentials_sync_id_unique", + "nullsNotDistinct": false, + "columns": [ + "sync_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ssh_folders": { + "name": "ssh_folders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "local_only": { + "name": "local_only", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ssh_folders_user_id": { + "name": "idx_ssh_folders_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ssh_folders_user_id_users_id_fk": { + "name": "ssh_folders_user_id_users_id_fk", + "tableFrom": "ssh_folders", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ssh_folders_sync_id_unique": { + "name": "ssh_folders_sync_id_unique", + "nullsNotDistinct": false, + "columns": [ + "sync_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sync_conflicts": { + "name": "sync_conflicts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "local_row": { + "name": "local_row", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "server_revision": { + "name": "server_revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_sync_conflicts_user": { + "name": "idx_sync_conflicts_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sync_conflicts_user_id_users_id_fk": { + "name": "sync_conflicts_user_id_users_id_fk", + "tableFrom": "sync_conflicts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sync_link": { + "name": "sync_link", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "server_url": { + "name": "server_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "server_name": { + "name": "server_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "server_version": { + "name": "server_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_token": { + "name": "session_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "custom_headers": { + "name": "custom_headers", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "basic_auth": { + "name": "basic_auth", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allow_invalid_certificate": { + "name": "allow_invalid_certificate", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "remote_user_id": { + "name": "remote_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_username": { + "name": "remote_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "account": { + "name": "account", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "known_types": { + "name": "known_types", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cursor": { + "name": "cursor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "linked_at": { + "name": "linked_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sync_link_user_id_users_id_fk": { + "name": "sync_link_user_id_users_id_fk", + "tableFrom": "sync_link", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sync_records": { + "name": "sync_records", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "sync_id": { + "name": "sync_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted": { + "name": "deleted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_hash": { + "name": "error_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_sync_records_user_entity_sync": { + "name": "idx_sync_records_user_entity_sync", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sync_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_sync_records_user_seq": { + "name": "idx_sync_records_user_seq", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sync_records_user_id_users_id_fk": { + "name": "sync_records_user_id_users_id_fk", + "tableFrom": "sync_records", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.trusted_devices": { + "name": "trusted_devices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "device_fingerprint": { + "name": "device_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_info": { + "name": "device_info", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_trusted_devices_user_id": { + "name": "idx_trusted_devices_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "trusted_devices_user_id_users_id_fk": { + "name": "trusted_devices_user_id_users_id_fk", + "tableFrom": "trusted_devices", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ui_preferences": { + "name": "ui_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "ui_preferences_user_id_users_id_fk": { + "name": "ui_preferences_user_id_users_id_fk", + "tableFrom": "ui_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_external_identities": { + "name": "user_external_identities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_external_identities_provider_subject": { + "name": "idx_user_external_identities_provider_subject", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_user_external_identities_user": { + "name": "idx_user_external_identities_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_external_identities_user_id_users_id_fk": { + "name": "user_external_identities_user_id_users_id_fk", + "tableFrom": "user_external_identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_open_tabs": { + "name": "user_open_tabs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "tab_type": { + "name": "tab_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tab_order": { + "name": "tab_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "backend_session_id": { + "name": "backend_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_open_tabs_user_id": { + "name": "idx_user_open_tabs_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_open_tabs_user_id_users_id_fk": { + "name": "user_open_tabs_user_id_users_id_fk", + "tableFrom": "user_open_tabs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_open_tabs_host_id_ssh_data_id_fk": { + "name": "user_open_tabs_host_id_ssh_data_id_fk", + "tableFrom": "user_open_tabs", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_preferences": { + "name": "user_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "reopen_tabs_on_login": { + "name": "reopen_tabs_on_login", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "font_size": { + "name": "font_size", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accent_color": { + "name": "accent_color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "storage_mode": { + "name": "storage_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command_palette_enabled": { + "name": "command_palette_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "show_host_tags": { + "name": "show_host_tags", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "host_tray_on_click": { + "name": "host_tray_on_click", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "pin_app_rail": { + "name": "pin_app_rail", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "expand_app_rail_on_hover": { + "name": "expand_app_rail_on_hover", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "show_pin_app_rail_button": { + "name": "show_pin_app_rail_button", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "folders_collapsed": { + "name": "folders_collapsed", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "disable_update_check": { + "name": "disable_update_check", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "confirm_tab_close": { + "name": "confirm_tab_close", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "hidden_rail_tabs": { + "name": "hidden_rail_tabs", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "compact_host_view": { + "name": "compact_host_view", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "status_color_scheme": { + "name": "status_color_scheme", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "custom_keybindings": { + "name": "custom_keybindings", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "user_preferences_user_id_users_id_fk": { + "name": "user_preferences_user_id_users_id_fk", + "tableFrom": "user_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_roles": { + "name": "user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "granted_by": { + "name": "granted_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_roles_user_role": { + "name": "idx_user_roles_user_role", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_user_roles_role_id": { + "name": "idx_user_roles_role_id", + "columns": [ + { + "expression": "role_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_granted_by_users_id_fk": { + "name": "user_roles_granted_by_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_second_factors": { + "name": "user_second_factors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "factor_id": { + "name": "factor_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "enrolled_at": { + "name": "enrolled_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_second_factors_user_factor": { + "name": "idx_user_second_factors_user_factor", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "factor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_second_factors_user_id_users_id_fk": { + "name": "user_second_factors_user_id_users_id_fk", + "tableFrom": "user_second_factors", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "registered_at": { + "name": "registered_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "donation_modal_dismissed": { + "name": "donation_modal_dismissed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/postgres/meta/_journal.json b/drizzle/postgres/meta/_journal.json index d60309508..20d26a13e 100644 --- a/drizzle/postgres/meta/_journal.json +++ b/drizzle/postgres/meta/_journal.json @@ -463,6 +463,20 @@ "when": 1790742086298, "tag": "0065_regular_joystick", "breakpoints": true + }, + { + "idx": 66, + "version": "7", + "when": 1791209295939, + "tag": "0066_retire_2_8_columns", + "breakpoints": true + }, + { + "idx": 67, + "version": "7", + "when": 1791209310758, + "tag": "0067_external_session_ref", + "breakpoints": true } ] } \ No newline at end of file diff --git a/drizzle/sqlite/0061_retire_2_8_columns.sql b/drizzle/sqlite/0061_retire_2_8_columns.sql new file mode 100644 index 000000000..6fcef5ed3 --- /dev/null +++ b/drizzle/sqlite/0061_retire_2_8_columns.sql @@ -0,0 +1,46 @@ +PRAGMA foreign_keys=OFF;--> statement-breakpoint +CREATE TABLE `__new_ssh_folders` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `user_id` text NOT NULL, + `name` text NOT NULL, + `color` text, + `icon` text, + `sort_order` integer, + `sync_id` text, + `local_only` integer DEFAULT false NOT NULL, + `created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL, + `updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL, + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +INSERT INTO `__new_ssh_folders`("id", "user_id", "name", "color", "icon", "sort_order", "sync_id", "local_only", "created_at", "updated_at") SELECT "id", "user_id", "name", "color", "icon", "sort_order", "sync_id", "local_only", "created_at", "updated_at" FROM `ssh_folders`;--> statement-breakpoint +DROP TABLE `ssh_folders`;--> statement-breakpoint +ALTER TABLE `__new_ssh_folders` RENAME TO `ssh_folders`;--> statement-breakpoint +PRAGMA foreign_keys=ON;--> statement-breakpoint +CREATE UNIQUE INDEX `ssh_folders_sync_id_unique` ON `ssh_folders` (`sync_id`);--> statement-breakpoint +CREATE INDEX `idx_ssh_folders_user_id` ON `ssh_folders` (`user_id`);--> statement-breakpoint +ALTER TABLE `ssh_data` DROP COLUMN `terminal_config`;--> statement-breakpoint +ALTER TABLE `ssh_data` DROP COLUMN `quick_actions`;--> statement-breakpoint +ALTER TABLE `sessions` DROP COLUMN `oidc_sub`;--> statement-breakpoint +ALTER TABLE `sessions` DROP COLUMN `oidc_sid`;--> statement-breakpoint +ALTER TABLE `sessions` DROP COLUMN `sso_provider_id`;--> statement-breakpoint +ALTER TABLE `user_preferences` DROP COLUMN `command_autocomplete`;--> statement-breakpoint +ALTER TABLE `user_preferences` DROP COLUMN `confirm_snippet_execution`;--> statement-breakpoint +ALTER TABLE `user_preferences` DROP COLUMN `custom_themes`;--> statement-breakpoint +ALTER TABLE `user_preferences` DROP COLUMN `terminal_defaults`;--> statement-breakpoint +ALTER TABLE `user_preferences` DROP COLUMN `terminal_macros`;--> statement-breakpoint +ALTER TABLE `users` DROP COLUMN `is_oidc`;--> statement-breakpoint +ALTER TABLE `users` DROP COLUMN `oidc_identifier`;--> statement-breakpoint +ALTER TABLE `users` DROP COLUMN `sso_provider_id`;--> statement-breakpoint +ALTER TABLE `users` DROP COLUMN `client_id`;--> statement-breakpoint +ALTER TABLE `users` DROP COLUMN `client_secret`;--> statement-breakpoint +ALTER TABLE `users` DROP COLUMN `issuer_url`;--> statement-breakpoint +ALTER TABLE `users` DROP COLUMN `authorization_url`;--> statement-breakpoint +ALTER TABLE `users` DROP COLUMN `token_url`;--> statement-breakpoint +ALTER TABLE `users` DROP COLUMN `identifier_path`;--> statement-breakpoint +ALTER TABLE `users` DROP COLUMN `name_path`;--> statement-breakpoint +ALTER TABLE `users` DROP COLUMN `scopes`;--> statement-breakpoint +DELETE FROM `settings` WHERE `key` IN ('host_defaults', 'analytics_enabled', 'analytics_instance_id');--> statement-breakpoint +UPDATE `ssh_data` SET `username` = REPLACE(`username`, '$oidc.preferred_username', '$external.username') WHERE `username` LIKE '%$oidc.preferred_username%';--> statement-breakpoint +UPDATE `ssh_credentials` SET `username` = REPLACE(`username`, '$oidc.preferred_username', '$external.username') WHERE `username` LIKE '%$oidc.preferred_username%';--> statement-breakpoint +UPDATE `host_defaults` SET `value` = REPLACE(`value`, '$oidc.preferred_username', '$external.username') WHERE `value` LIKE '%$oidc.preferred_username%'; \ No newline at end of file diff --git a/drizzle/sqlite/0062_external_session_ref.sql b/drizzle/sqlite/0062_external_session_ref.sql new file mode 100644 index 000000000..f8b8402a8 --- /dev/null +++ b/drizzle/sqlite/0062_external_session_ref.sql @@ -0,0 +1 @@ +ALTER TABLE `sessions` ADD `external_session_ref` text; \ No newline at end of file diff --git a/drizzle/sqlite/meta/0061_snapshot.json b/drizzle/sqlite/meta/0061_snapshot.json new file mode 100644 index 000000000..22d34e0bd --- /dev/null +++ b/drizzle/sqlite/meta/0061_snapshot.json @@ -0,0 +1,4452 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "6c43dde1-c5eb-4ff0-8c71-b4c5e6010c66", + "prevId": "d81ae027-ff86-4354-9705-ead08a426d8f", + "tables": { + "api_keys": { + "name": "api_keys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_prefix": { + "name": "token_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": { + "idx_api_keys_user_id": { + "name": "idx_api_keys_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "api_keys_user_id_users_id_fk": { + "name": "api_keys_user_id_users_id_fk", + "tableFrom": "api_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "audit_logs": { + "name": "audit_logs", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resource_name": { + "name": "resource_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "success": { + "name": "success", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_audit_logs_timestamp": { + "name": "idx_audit_logs_timestamp", + "columns": [ + "timestamp" + ], + "isUnique": false + }, + "idx_audit_logs_user_ts": { + "name": "idx_audit_logs_user_ts", + "columns": [ + "user_id", + "timestamp" + ], + "isUnique": false + }, + "idx_audit_logs_action_ts": { + "name": "idx_audit_logs_action_ts", + "columns": [ + "action", + "timestamp" + ], + "isUnique": false + }, + "idx_audit_logs_resource_ts": { + "name": "idx_audit_logs_resource_ts", + "columns": [ + "resource_type", + "timestamp" + ], + "isUnique": false + } + }, + "foreignKeys": { + "audit_logs_user_id_users_id_fk": { + "name": "audit_logs_user_id_users_id_fk", + "tableFrom": "audit_logs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "credential_access": { + "name": "credential_access", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'use'" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_credential_access_user_id": { + "name": "idx_credential_access_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_credential_access_role_id": { + "name": "idx_credential_access_role_id", + "columns": [ + "role_id" + ], + "isUnique": false + }, + "idx_credential_access_credential_id": { + "name": "idx_credential_access_credential_id", + "columns": [ + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "credential_access_credential_id_ssh_credentials_id_fk": { + "name": "credential_access_credential_id_ssh_credentials_id_fk", + "tableFrom": "credential_access", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_user_id_users_id_fk": { + "name": "credential_access_user_id_users_id_fk", + "tableFrom": "credential_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_role_id_roles_id_fk": { + "name": "credential_access_role_id_roles_id_fk", + "tableFrom": "credential_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_granted_by_users_id_fk": { + "name": "credential_access_granted_by_users_id_fk", + "tableFrom": "credential_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "credential_sidebar_preferences": { + "name": "credential_sidebar_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "credential_sidebar_preferences_user_id_users_id_fk": { + "name": "credential_sidebar_preferences_user_id_users_id_fk", + "tableFrom": "credential_sidebar_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "folder_access": { + "name": "folder_access", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'connect'" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_folder_access_owner_folder": { + "name": "idx_folder_access_owner_folder", + "columns": [ + "owner_user_id", + "folder" + ], + "isUnique": false + } + }, + "foreignKeys": { + "folder_access_owner_user_id_users_id_fk": { + "name": "folder_access_owner_user_id_users_id_fk", + "tableFrom": "folder_access", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_access_user_id_users_id_fk": { + "name": "folder_access_user_id_users_id_fk", + "tableFrom": "folder_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_access_role_id_roles_id_fk": { + "name": "folder_access_role_id_roles_id_fk", + "tableFrom": "folder_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_access_granted_by_users_id_fk": { + "name": "folder_access_granted_by_users_id_fk", + "tableFrom": "folder_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "host_access": { + "name": "host_access", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'connect'" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "last_accessed_at": { + "name": "last_accessed_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_count": { + "name": "access_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "idx_host_access_user_id": { + "name": "idx_host_access_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_host_access_role_id": { + "name": "idx_host_access_role_id", + "columns": [ + "role_id" + ], + "isUnique": false + }, + "idx_host_access_host_id": { + "name": "idx_host_access_host_id", + "columns": [ + "host_id" + ], + "isUnique": false + }, + "idx_host_access_expires_at": { + "name": "idx_host_access_expires_at", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "host_access_host_id_ssh_data_id_fk": { + "name": "host_access_host_id_ssh_data_id_fk", + "tableFrom": "host_access", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_access_user_id_users_id_fk": { + "name": "host_access_user_id_users_id_fk", + "tableFrom": "host_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_access_role_id_roles_id_fk": { + "name": "host_access_role_id_roles_id_fk", + "tableFrom": "host_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_access_granted_by_users_id_fk": { + "name": "host_access_granted_by_users_id_fk", + "tableFrom": "host_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "host_defaults": { + "name": "host_defaults", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope_key": { + "name": "scope_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folder_id": { + "name": "folder_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_host_defaults_scope_key": { + "name": "idx_host_defaults_scope_key", + "columns": [ + "scope_key", + "namespace", + "key" + ], + "isUnique": true + }, + "idx_host_defaults_user": { + "name": "idx_host_defaults_user", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_host_defaults_folder": { + "name": "idx_host_defaults_folder", + "columns": [ + "folder_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "host_defaults_user_id_users_id_fk": { + "name": "host_defaults_user_id_users_id_fk", + "tableFrom": "host_defaults", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_defaults_folder_id_ssh_folders_id_fk": { + "name": "host_defaults_folder_id_ssh_folders_id_fk", + "tableFrom": "host_defaults", + "tableTo": "ssh_folders", + "columnsFrom": [ + "folder_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "host_protocol_auth": { + "name": "host_protocol_auth", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'direct'" + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fields": { + "name": "fields", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "secret_fields": { + "name": "secret_fields", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_host_protocol_auth_host_protocol": { + "name": "idx_host_protocol_auth_host_protocol", + "columns": [ + "host_id", + "protocol" + ], + "isUnique": true + }, + "idx_host_protocol_auth_user": { + "name": "idx_host_protocol_auth_user", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_host_protocol_auth_credential": { + "name": "idx_host_protocol_auth_credential", + "columns": [ + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "host_protocol_auth_host_id_ssh_data_id_fk": { + "name": "host_protocol_auth_host_id_ssh_data_id_fk", + "tableFrom": "host_protocol_auth", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_protocol_auth_user_id_users_id_fk": { + "name": "host_protocol_auth_user_id_users_id_fk", + "tableFrom": "host_protocol_auth", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_protocol_auth_credential_id_ssh_credentials_id_fk": { + "name": "host_protocol_auth_credential_id_ssh_credentials_id_fk", + "tableFrom": "host_protocol_auth", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "host_sidebar_preferences": { + "name": "host_sidebar_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "host_sidebar_preferences_user_id_users_id_fk": { + "name": "host_sidebar_preferences_user_id_users_id_fk", + "tableFrom": "host_sidebar_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ssh_data": { + "name": "ssh_data", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "connection_type": { + "name": "connection_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ssh'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parent_host_id": { + "name": "parent_host_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pin": { + "name": "pin", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "share_ssh_auth": { + "name": "share_ssh_auth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "force_keyboard_interactive": { + "name": "force_keyboard_interactive", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text(8192)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_password": { + "name": "key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sudo_password": { + "name": "sudo_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "override_credential_username": { + "name": "override_credential_username", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "jump_hosts": { + "name": "jump_hosts", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_check_enabled": { + "name": "status_check_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "status_check_interval": { + "name": "status_check_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ssh_options": { + "name": "ssh_options", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enable_ssh": { + "name": "enable_ssh", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "ssh_port": { + "name": "ssh_port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 22 + }, + "use_socks5": { + "name": "use_socks5", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_host": { + "name": "socks5_host", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_port": { + "name": "socks5_port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_username": { + "name": "socks5_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_password": { + "name": "socks5_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_proxy_chain": { + "name": "socks5_proxy_chain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "connection_origin": { + "name": "connection_origin", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "port_knock_sequence": { + "name": "port_knock_sequence", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_fingerprint": { + "name": "host_key_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_type": { + "name": "host_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_algorithm": { + "name": "host_key_algorithm", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'sha256'" + }, + "host_key_first_seen": { + "name": "host_key_first_seen", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_last_verified": { + "name": "host_key_last_verified", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_changed_count": { + "name": "host_key_changed_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "local_only": { + "name": "local_only", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "shared_source": { + "name": "shared_source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_overrides": { + "name": "default_overrides", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "ssh_data_sync_id_unique": { + "name": "ssh_data_sync_id_unique", + "columns": [ + "sync_id" + ], + "isUnique": true + }, + "idx_ssh_data_user_id": { + "name": "idx_ssh_data_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_ssh_data_parent_host": { + "name": "idx_ssh_data_parent_host", + "columns": [ + "parent_host_id" + ], + "isUnique": false + }, + "idx_ssh_data_credential": { + "name": "idx_ssh_data_credential", + "columns": [ + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_data_user_id_users_id_fk": { + "name": "ssh_data_user_id_users_id_fk", + "tableFrom": "ssh_data", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_data_parent_host_id_ssh_data_id_fk": { + "name": "ssh_data_parent_host_id_ssh_data_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_data", + "columnsFrom": [ + "parent_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_install_counts": { + "name": "plugin_install_counts", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "registry_id": { + "name": "registry_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'aggregate-telemetry'" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_install_counts_plugin_registry": { + "name": "idx_plugin_install_counts_plugin_registry", + "columns": [ + "plugin_id", + "registry_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_migrations": { + "name": "plugin_migrations", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "migration_id": { + "name": "migration_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_migrations_plugin_migration": { + "name": "idx_plugin_migrations_plugin_migration", + "columns": [ + "plugin_id", + "migration_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_migrations_plugin_id_plugins_id_fk": { + "name": "plugin_migrations_plugin_id_plugins_id_fk", + "tableFrom": "plugin_migrations", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_permission_grants": { + "name": "plugin_permission_grants", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "capability": { + "name": "capability", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'admin'" + } + }, + "indexes": { + "idx_plugin_permission_grants_plugin_capability": { + "name": "idx_plugin_permission_grants_plugin_capability", + "columns": [ + "plugin_id", + "capability" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_permission_grants_plugin_id_plugins_id_fk": { + "name": "plugin_permission_grants_plugin_id_plugins_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_permission_grants_granted_by_users_id_fk": { + "name": "plugin_permission_grants_granted_by_users_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_registries": { + "name": "plugin_registries", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'community'" + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "signing_key": { + "name": "signing_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_checked_at": { + "name": "last_checked_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_index_hash": { + "name": "last_index_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_settings": { + "name": "plugin_settings", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted": { + "name": "encrypted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_settings_scope_key": { + "name": "idx_plugin_settings_scope_key", + "columns": [ + "plugin_id", + "scope", + "scope_id", + "key" + ], + "isUnique": true + }, + "idx_plugin_settings_plugin_scope": { + "name": "idx_plugin_settings_plugin_scope", + "columns": [ + "plugin_id", + "scope" + ], + "isUnique": false + } + }, + "foreignKeys": { + "plugin_settings_plugin_id_plugins_id_fk": { + "name": "plugin_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_storage": { + "name": "plugin_storage", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_storage_plugin_key": { + "name": "idx_plugin_storage_plugin_key", + "columns": [ + "plugin_id", + "key" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_storage_plugin_id_plugins_id_fk": { + "name": "plugin_storage_plugin_id_plugins_id_fk", + "tableFrom": "plugin_storage", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugins": { + "name": "plugins", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'available'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'community'" + }, + "registry_id": { + "name": "registry_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'disabled'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "installed_at": { + "name": "installed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "auto_update": { + "name": "auto_update", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "manifest_json": { + "name": "manifest_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_plugins_registry_id": { + "name": "idx_plugins_registry_id", + "columns": [ + "registry_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "rbac_applied_defaults": { + "name": "rbac_applied_defaults", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "role_name": { + "name": "role_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_rbac_applied_defaults_role_permission": { + "name": "idx_rbac_applied_defaults_role_permission", + "columns": [ + "role_name", + "permission" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "rbac_known_permissions": { + "name": "rbac_known_permissions", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_rbac_known_permissions_permission": { + "name": "idx_rbac_known_permissions_permission", + "columns": [ + "permission" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "recent_activity": { + "name": "recent_activity", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_name": { + "name": "host_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_recent_activity_user_ts": { + "name": "idx_recent_activity_user_ts", + "columns": [ + "user_id", + "timestamp" + ], + "isUnique": false + } + }, + "foreignKeys": { + "recent_activity_user_id_users_id_fk": { + "name": "recent_activity_user_id_users_id_fk", + "tableFrom": "recent_activity", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "recent_activity_host_id_ssh_data_id_fk": { + "name": "recent_activity_host_id_ssh_data_id_fk", + "tableFrom": "recent_activity", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "roles": { + "name": "roles", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_system": { + "name": "is_system", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "roles_name_unique": { + "name": "roles_name_unique", + "columns": [ + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sessions": { + "name": "sessions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "jwt_token": { + "name": "jwt_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_info": { + "name": "device_info", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_sessions_user_id": { + "name": "idx_sessions_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_sessions_expires_at": { + "name": "idx_sessions_expires_at", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "settings": { + "name": "settings", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shared_credential_secrets": { + "name": "shared_credential_secrets", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "credential_access_id": { + "name": "credential_access_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_user_id": { + "name": "target_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'password'" + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text(16384)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text(4096)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text(8192)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_shared_credential_secrets_scope": { + "name": "idx_shared_credential_secrets_scope", + "columns": [ + "credential_access_id", + "target_user_id" + ], + "isUnique": true + }, + "idx_shared_credential_secrets_target": { + "name": "idx_shared_credential_secrets_target", + "columns": [ + "target_user_id", + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "shared_credential_secrets_target_user_id_users_id_fk": { + "name": "shared_credential_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_credential_secrets_credential_id_ssh_credentials_id_fk": { + "name": "shared_credential_secrets_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_cred_secrets_access_id_fk": { + "name": "shared_cred_secrets_access_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "credential_access", + "columnsFrom": [ + "credential_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shared_host_auth_overrides": { + "name": "shared_host_auth_overrides", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ssh'" + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "shared_host_auth_overrides_host_user_protocol_unique": { + "name": "shared_host_auth_overrides_host_user_protocol_unique", + "columns": [ + "host_id", + "user_id", + "protocol" + ], + "isUnique": true + } + }, + "foreignKeys": { + "shared_host_auth_overrides_host_id_ssh_data_id_fk": { + "name": "shared_host_auth_overrides_host_id_ssh_data_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_user_id_users_id_fk": { + "name": "shared_host_auth_overrides_user_id_users_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shared_host_secrets": { + "name": "shared_host_secrets", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "host_access_id": { + "name": "host_access_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_user_id": { + "name": "target_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ssh'" + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'credential'" + }, + "original_credential_id": { + "name": "original_credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_auth_type": { + "name": "encrypted_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text(16384)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_type": { + "name": "encrypted_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_domain": { + "name": "encrypted_domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_fields": { + "name": "encrypted_fields", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_shared_host_secrets_scope": { + "name": "idx_shared_host_secrets_scope", + "columns": [ + "host_access_id", + "target_user_id", + "protocol" + ], + "isUnique": true + } + }, + "foreignKeys": { + "shared_host_secrets_host_access_id_host_access_id_fk": { + "name": "shared_host_secrets_host_access_id_host_access_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "host_access", + "columnsFrom": [ + "host_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_target_user_id_users_id_fk": { + "name": "shared_host_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_original_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_secrets_original_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "original_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ssh_credential_usage": { + "name": "ssh_credential_usage", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "used_at": { + "name": "used_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ssh_credential_usage_credential": { + "name": "idx_ssh_credential_usage_credential", + "columns": [ + "credential_id" + ], + "isUnique": false + }, + "idx_ssh_credential_usage_user": { + "name": "idx_ssh_credential_usage_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_credential_usage_credential_id_ssh_credentials_id_fk": { + "name": "ssh_credential_usage_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_host_id_ssh_data_id_fk": { + "name": "ssh_credential_usage_host_id_ssh_data_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_user_id_users_id_fk": { + "name": "ssh_credential_usage_user_id_users_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ssh_credentials": { + "name": "ssh_credentials", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pin": { + "name": "pin", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text(16384)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text(16384)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text(4096)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_password": { + "name": "key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "detected_key_type": { + "name": "detected_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text(8192)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "usage_count": { + "name": "usage_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "shared_source": { + "name": "shared_source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "ssh_credentials_sync_id_unique": { + "name": "ssh_credentials_sync_id_unique", + "columns": [ + "sync_id" + ], + "isUnique": true + }, + "idx_ssh_credentials_user_id": { + "name": "idx_ssh_credentials_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_credentials_user_id_users_id_fk": { + "name": "ssh_credentials_user_id_users_id_fk", + "tableFrom": "ssh_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ssh_folders": { + "name": "ssh_folders", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "local_only": { + "name": "local_only", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "ssh_folders_sync_id_unique": { + "name": "ssh_folders_sync_id_unique", + "columns": [ + "sync_id" + ], + "isUnique": true + }, + "idx_ssh_folders_user_id": { + "name": "idx_ssh_folders_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_folders_user_id_users_id_fk": { + "name": "ssh_folders_user_id_users_id_fk", + "tableFrom": "ssh_folders", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sync_conflicts": { + "name": "sync_conflicts", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "local_row": { + "name": "local_row", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "server_revision": { + "name": "server_revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_sync_conflicts_user": { + "name": "idx_sync_conflicts_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "sync_conflicts_user_id_users_id_fk": { + "name": "sync_conflicts_user_id_users_id_fk", + "tableFrom": "sync_conflicts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sync_link": { + "name": "sync_link", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "server_url": { + "name": "server_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "server_name": { + "name": "server_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "server_version": { + "name": "server_version", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_token": { + "name": "session_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "custom_headers": { + "name": "custom_headers", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "basic_auth": { + "name": "basic_auth", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "allow_invalid_certificate": { + "name": "allow_invalid_certificate", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "remote_user_id": { + "name": "remote_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "remote_username": { + "name": "remote_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "account": { + "name": "account", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "known_types": { + "name": "known_types", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cursor": { + "name": "cursor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'idle'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "linked_at": { + "name": "linked_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "sync_link_user_id_users_id_fk": { + "name": "sync_link_user_id_users_id_fk", + "tableFrom": "sync_link", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sync_records": { + "name": "sync_records", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted": { + "name": "deleted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_hash": { + "name": "error_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_sync_records_user_entity_sync": { + "name": "idx_sync_records_user_entity_sync", + "columns": [ + "user_id", + "entity_type", + "sync_id" + ], + "isUnique": true + }, + "idx_sync_records_user_seq": { + "name": "idx_sync_records_user_seq", + "columns": [ + "user_id", + "seq" + ], + "isUnique": false + } + }, + "foreignKeys": { + "sync_records_user_id_users_id_fk": { + "name": "sync_records_user_id_users_id_fk", + "tableFrom": "sync_records", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "trusted_devices": { + "name": "trusted_devices", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_fingerprint": { + "name": "device_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_info": { + "name": "device_info", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_trusted_devices_user_id": { + "name": "idx_trusted_devices_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "trusted_devices_user_id_users_id_fk": { + "name": "trusted_devices_user_id_users_id_fk", + "tableFrom": "trusted_devices", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ui_preferences": { + "name": "ui_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "ui_preferences_user_id_users_id_fk": { + "name": "ui_preferences_user_id_users_id_fk", + "tableFrom": "ui_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_external_identities": { + "name": "user_external_identities", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_external_identities_provider_subject": { + "name": "idx_user_external_identities_provider_subject", + "columns": [ + "provider_id", + "subject" + ], + "isUnique": true + }, + "idx_user_external_identities_user": { + "name": "idx_user_external_identities_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_external_identities_user_id_users_id_fk": { + "name": "user_external_identities_user_id_users_id_fk", + "tableFrom": "user_external_identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_open_tabs": { + "name": "user_open_tabs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_type": { + "name": "tab_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_order": { + "name": "tab_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "backend_session_id": { + "name": "backend_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_open_tabs_user_id": { + "name": "idx_user_open_tabs_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_open_tabs_user_id_users_id_fk": { + "name": "user_open_tabs_user_id_users_id_fk", + "tableFrom": "user_open_tabs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_open_tabs_host_id_ssh_data_id_fk": { + "name": "user_open_tabs_host_id_ssh_data_id_fk", + "tableFrom": "user_open_tabs", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_preferences": { + "name": "user_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "reopen_tabs_on_login": { + "name": "reopen_tabs_on_login", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "font_size": { + "name": "font_size", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "accent_color": { + "name": "accent_color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "storage_mode": { + "name": "storage_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "command_palette_enabled": { + "name": "command_palette_enabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "show_host_tags": { + "name": "show_host_tags", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_tray_on_click": { + "name": "host_tray_on_click", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pin_app_rail": { + "name": "pin_app_rail", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expand_app_rail_on_hover": { + "name": "expand_app_rail_on_hover", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "show_pin_app_rail_button": { + "name": "show_pin_app_rail_button", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folders_collapsed": { + "name": "folders_collapsed", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "disable_update_check": { + "name": "disable_update_check", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "confirm_tab_close": { + "name": "confirm_tab_close", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hidden_rail_tabs": { + "name": "hidden_rail_tabs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "compact_host_view": { + "name": "compact_host_view", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_color_scheme": { + "name": "status_color_scheme", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "custom_keybindings": { + "name": "custom_keybindings", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "user_preferences_user_id_users_id_fk": { + "name": "user_preferences_user_id_users_id_fk", + "tableFrom": "user_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_roles": { + "name": "user_roles", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_roles_user_role": { + "name": "idx_user_roles_user_role", + "columns": [ + "user_id", + "role_id" + ], + "isUnique": true + }, + "idx_user_roles_role_id": { + "name": "idx_user_roles_role_id", + "columns": [ + "role_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_granted_by_users_id_fk": { + "name": "user_roles_granted_by_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_second_factors": { + "name": "user_second_factors", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "factor_id": { + "name": "factor_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enrolled_at": { + "name": "enrolled_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_second_factors_user_factor": { + "name": "idx_user_second_factors_user_factor", + "columns": [ + "user_id", + "plugin_id", + "factor_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "user_second_factors_user_id_users_id_fk": { + "name": "user_second_factors_user_id_users_id_fk", + "tableFrom": "user_second_factors", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_admin": { + "name": "is_admin", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "registered_at": { + "name": "registered_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "donation_modal_dismissed": { + "name": "donation_modal_dismissed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/sqlite/meta/0062_snapshot.json b/drizzle/sqlite/meta/0062_snapshot.json new file mode 100644 index 000000000..df8ccf836 --- /dev/null +++ b/drizzle/sqlite/meta/0062_snapshot.json @@ -0,0 +1,4459 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "dd7d607e-a0a7-4fa9-833b-d333ceed8cee", + "prevId": "6c43dde1-c5eb-4ff0-8c71-b4c5e6010c66", + "tables": { + "api_keys": { + "name": "api_keys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_prefix": { + "name": "token_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": { + "idx_api_keys_user_id": { + "name": "idx_api_keys_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "api_keys_user_id_users_id_fk": { + "name": "api_keys_user_id_users_id_fk", + "tableFrom": "api_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "audit_logs": { + "name": "audit_logs", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resource_name": { + "name": "resource_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "success": { + "name": "success", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_audit_logs_timestamp": { + "name": "idx_audit_logs_timestamp", + "columns": [ + "timestamp" + ], + "isUnique": false + }, + "idx_audit_logs_user_ts": { + "name": "idx_audit_logs_user_ts", + "columns": [ + "user_id", + "timestamp" + ], + "isUnique": false + }, + "idx_audit_logs_action_ts": { + "name": "idx_audit_logs_action_ts", + "columns": [ + "action", + "timestamp" + ], + "isUnique": false + }, + "idx_audit_logs_resource_ts": { + "name": "idx_audit_logs_resource_ts", + "columns": [ + "resource_type", + "timestamp" + ], + "isUnique": false + } + }, + "foreignKeys": { + "audit_logs_user_id_users_id_fk": { + "name": "audit_logs_user_id_users_id_fk", + "tableFrom": "audit_logs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "credential_access": { + "name": "credential_access", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'use'" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_credential_access_user_id": { + "name": "idx_credential_access_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_credential_access_role_id": { + "name": "idx_credential_access_role_id", + "columns": [ + "role_id" + ], + "isUnique": false + }, + "idx_credential_access_credential_id": { + "name": "idx_credential_access_credential_id", + "columns": [ + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "credential_access_credential_id_ssh_credentials_id_fk": { + "name": "credential_access_credential_id_ssh_credentials_id_fk", + "tableFrom": "credential_access", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_user_id_users_id_fk": { + "name": "credential_access_user_id_users_id_fk", + "tableFrom": "credential_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_role_id_roles_id_fk": { + "name": "credential_access_role_id_roles_id_fk", + "tableFrom": "credential_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_access_granted_by_users_id_fk": { + "name": "credential_access_granted_by_users_id_fk", + "tableFrom": "credential_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "credential_sidebar_preferences": { + "name": "credential_sidebar_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "credential_sidebar_preferences_user_id_users_id_fk": { + "name": "credential_sidebar_preferences_user_id_users_id_fk", + "tableFrom": "credential_sidebar_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "folder_access": { + "name": "folder_access", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'connect'" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_folder_access_owner_folder": { + "name": "idx_folder_access_owner_folder", + "columns": [ + "owner_user_id", + "folder" + ], + "isUnique": false + } + }, + "foreignKeys": { + "folder_access_owner_user_id_users_id_fk": { + "name": "folder_access_owner_user_id_users_id_fk", + "tableFrom": "folder_access", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_access_user_id_users_id_fk": { + "name": "folder_access_user_id_users_id_fk", + "tableFrom": "folder_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_access_role_id_roles_id_fk": { + "name": "folder_access_role_id_roles_id_fk", + "tableFrom": "folder_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_access_granted_by_users_id_fk": { + "name": "folder_access_granted_by_users_id_fk", + "tableFrom": "folder_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "host_access": { + "name": "host_access", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission_level": { + "name": "permission_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'connect'" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "last_accessed_at": { + "name": "last_accessed_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_count": { + "name": "access_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "idx_host_access_user_id": { + "name": "idx_host_access_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_host_access_role_id": { + "name": "idx_host_access_role_id", + "columns": [ + "role_id" + ], + "isUnique": false + }, + "idx_host_access_host_id": { + "name": "idx_host_access_host_id", + "columns": [ + "host_id" + ], + "isUnique": false + }, + "idx_host_access_expires_at": { + "name": "idx_host_access_expires_at", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "host_access_host_id_ssh_data_id_fk": { + "name": "host_access_host_id_ssh_data_id_fk", + "tableFrom": "host_access", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_access_user_id_users_id_fk": { + "name": "host_access_user_id_users_id_fk", + "tableFrom": "host_access", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_access_role_id_roles_id_fk": { + "name": "host_access_role_id_roles_id_fk", + "tableFrom": "host_access", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_access_granted_by_users_id_fk": { + "name": "host_access_granted_by_users_id_fk", + "tableFrom": "host_access", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "host_defaults": { + "name": "host_defaults", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope_key": { + "name": "scope_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folder_id": { + "name": "folder_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_host_defaults_scope_key": { + "name": "idx_host_defaults_scope_key", + "columns": [ + "scope_key", + "namespace", + "key" + ], + "isUnique": true + }, + "idx_host_defaults_user": { + "name": "idx_host_defaults_user", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_host_defaults_folder": { + "name": "idx_host_defaults_folder", + "columns": [ + "folder_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "host_defaults_user_id_users_id_fk": { + "name": "host_defaults_user_id_users_id_fk", + "tableFrom": "host_defaults", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_defaults_folder_id_ssh_folders_id_fk": { + "name": "host_defaults_folder_id_ssh_folders_id_fk", + "tableFrom": "host_defaults", + "tableTo": "ssh_folders", + "columnsFrom": [ + "folder_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "host_protocol_auth": { + "name": "host_protocol_auth", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'direct'" + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fields": { + "name": "fields", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "secret_fields": { + "name": "secret_fields", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_host_protocol_auth_host_protocol": { + "name": "idx_host_protocol_auth_host_protocol", + "columns": [ + "host_id", + "protocol" + ], + "isUnique": true + }, + "idx_host_protocol_auth_user": { + "name": "idx_host_protocol_auth_user", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_host_protocol_auth_credential": { + "name": "idx_host_protocol_auth_credential", + "columns": [ + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "host_protocol_auth_host_id_ssh_data_id_fk": { + "name": "host_protocol_auth_host_id_ssh_data_id_fk", + "tableFrom": "host_protocol_auth", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_protocol_auth_user_id_users_id_fk": { + "name": "host_protocol_auth_user_id_users_id_fk", + "tableFrom": "host_protocol_auth", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "host_protocol_auth_credential_id_ssh_credentials_id_fk": { + "name": "host_protocol_auth_credential_id_ssh_credentials_id_fk", + "tableFrom": "host_protocol_auth", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "host_sidebar_preferences": { + "name": "host_sidebar_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "host_sidebar_preferences_user_id_users_id_fk": { + "name": "host_sidebar_preferences_user_id_users_id_fk", + "tableFrom": "host_sidebar_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ssh_data": { + "name": "ssh_data", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "connection_type": { + "name": "connection_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ssh'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parent_host_id": { + "name": "parent_host_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pin": { + "name": "pin", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "share_ssh_auth": { + "name": "share_ssh_auth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "force_keyboard_interactive": { + "name": "force_keyboard_interactive", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text(8192)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_password": { + "name": "key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sudo_password": { + "name": "sudo_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "override_credential_username": { + "name": "override_credential_username", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "jump_hosts": { + "name": "jump_hosts", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_check_enabled": { + "name": "status_check_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "status_check_interval": { + "name": "status_check_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ssh_options": { + "name": "ssh_options", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enable_ssh": { + "name": "enable_ssh", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "ssh_port": { + "name": "ssh_port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 22 + }, + "use_socks5": { + "name": "use_socks5", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_host": { + "name": "socks5_host", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_port": { + "name": "socks5_port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_username": { + "name": "socks5_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_password": { + "name": "socks5_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socks5_proxy_chain": { + "name": "socks5_proxy_chain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "connection_origin": { + "name": "connection_origin", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "port_knock_sequence": { + "name": "port_knock_sequence", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_fingerprint": { + "name": "host_key_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_type": { + "name": "host_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_algorithm": { + "name": "host_key_algorithm", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'sha256'" + }, + "host_key_first_seen": { + "name": "host_key_first_seen", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_last_verified": { + "name": "host_key_last_verified", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_key_changed_count": { + "name": "host_key_changed_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "local_only": { + "name": "local_only", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "shared_source": { + "name": "shared_source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_overrides": { + "name": "default_overrides", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "ssh_data_sync_id_unique": { + "name": "ssh_data_sync_id_unique", + "columns": [ + "sync_id" + ], + "isUnique": true + }, + "idx_ssh_data_user_id": { + "name": "idx_ssh_data_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_ssh_data_parent_host": { + "name": "idx_ssh_data_parent_host", + "columns": [ + "parent_host_id" + ], + "isUnique": false + }, + "idx_ssh_data_credential": { + "name": "idx_ssh_data_credential", + "columns": [ + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_data_user_id_users_id_fk": { + "name": "ssh_data_user_id_users_id_fk", + "tableFrom": "ssh_data", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_data_parent_host_id_ssh_data_id_fk": { + "name": "ssh_data_parent_host_id_ssh_data_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_data", + "columnsFrom": [ + "parent_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_install_counts": { + "name": "plugin_install_counts", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "registry_id": { + "name": "registry_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'aggregate-telemetry'" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_install_counts_plugin_registry": { + "name": "idx_plugin_install_counts_plugin_registry", + "columns": [ + "plugin_id", + "registry_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_migrations": { + "name": "plugin_migrations", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "migration_id": { + "name": "migration_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_migrations_plugin_migration": { + "name": "idx_plugin_migrations_plugin_migration", + "columns": [ + "plugin_id", + "migration_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_migrations_plugin_id_plugins_id_fk": { + "name": "plugin_migrations_plugin_id_plugins_id_fk", + "tableFrom": "plugin_migrations", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_permission_grants": { + "name": "plugin_permission_grants", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "capability": { + "name": "capability", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'admin'" + } + }, + "indexes": { + "idx_plugin_permission_grants_plugin_capability": { + "name": "idx_plugin_permission_grants_plugin_capability", + "columns": [ + "plugin_id", + "capability" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_permission_grants_plugin_id_plugins_id_fk": { + "name": "plugin_permission_grants_plugin_id_plugins_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_permission_grants_granted_by_users_id_fk": { + "name": "plugin_permission_grants_granted_by_users_id_fk", + "tableFrom": "plugin_permission_grants", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_registries": { + "name": "plugin_registries", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'community'" + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "signing_key": { + "name": "signing_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_checked_at": { + "name": "last_checked_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_index_hash": { + "name": "last_index_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_settings": { + "name": "plugin_settings", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted": { + "name": "encrypted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_settings_scope_key": { + "name": "idx_plugin_settings_scope_key", + "columns": [ + "plugin_id", + "scope", + "scope_id", + "key" + ], + "isUnique": true + }, + "idx_plugin_settings_plugin_scope": { + "name": "idx_plugin_settings_plugin_scope", + "columns": [ + "plugin_id", + "scope" + ], + "isUnique": false + } + }, + "foreignKeys": { + "plugin_settings_plugin_id_plugins_id_fk": { + "name": "plugin_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_storage": { + "name": "plugin_storage", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_plugin_storage_plugin_key": { + "name": "idx_plugin_storage_plugin_key", + "columns": [ + "plugin_id", + "key" + ], + "isUnique": true + } + }, + "foreignKeys": { + "plugin_storage_plugin_id_plugins_id_fk": { + "name": "plugin_storage_plugin_id_plugins_id_fk", + "tableFrom": "plugin_storage", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugins": { + "name": "plugins", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'available'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'community'" + }, + "registry_id": { + "name": "registry_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'disabled'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "installed_at": { + "name": "installed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "auto_update": { + "name": "auto_update", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "manifest_json": { + "name": "manifest_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_plugins_registry_id": { + "name": "idx_plugins_registry_id", + "columns": [ + "registry_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "rbac_applied_defaults": { + "name": "rbac_applied_defaults", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "role_name": { + "name": "role_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "applied_at": { + "name": "applied_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_rbac_applied_defaults_role_permission": { + "name": "idx_rbac_applied_defaults_role_permission", + "columns": [ + "role_name", + "permission" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "rbac_known_permissions": { + "name": "rbac_known_permissions", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_rbac_known_permissions_permission": { + "name": "idx_rbac_known_permissions_permission", + "columns": [ + "permission" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "recent_activity": { + "name": "recent_activity", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_name": { + "name": "host_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_recent_activity_user_ts": { + "name": "idx_recent_activity_user_ts", + "columns": [ + "user_id", + "timestamp" + ], + "isUnique": false + } + }, + "foreignKeys": { + "recent_activity_user_id_users_id_fk": { + "name": "recent_activity_user_id_users_id_fk", + "tableFrom": "recent_activity", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "recent_activity_host_id_ssh_data_id_fk": { + "name": "recent_activity_host_id_ssh_data_id_fk", + "tableFrom": "recent_activity", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "roles": { + "name": "roles", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_system": { + "name": "is_system", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "roles_name_unique": { + "name": "roles_name_unique", + "columns": [ + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sessions": { + "name": "sessions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "jwt_token": { + "name": "jwt_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_info": { + "name": "device_info", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "external_session_ref": { + "name": "external_session_ref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_sessions_user_id": { + "name": "idx_sessions_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_sessions_expires_at": { + "name": "idx_sessions_expires_at", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "settings": { + "name": "settings", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shared_credential_secrets": { + "name": "shared_credential_secrets", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "credential_access_id": { + "name": "credential_access_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_user_id": { + "name": "target_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'password'" + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text(16384)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text(4096)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text(8192)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_shared_credential_secrets_scope": { + "name": "idx_shared_credential_secrets_scope", + "columns": [ + "credential_access_id", + "target_user_id" + ], + "isUnique": true + }, + "idx_shared_credential_secrets_target": { + "name": "idx_shared_credential_secrets_target", + "columns": [ + "target_user_id", + "credential_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "shared_credential_secrets_target_user_id_users_id_fk": { + "name": "shared_credential_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_credential_secrets_credential_id_ssh_credentials_id_fk": { + "name": "shared_credential_secrets_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_cred_secrets_access_id_fk": { + "name": "shared_cred_secrets_access_id_fk", + "tableFrom": "shared_credential_secrets", + "tableTo": "credential_access", + "columnsFrom": [ + "credential_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shared_host_auth_overrides": { + "name": "shared_host_auth_overrides", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ssh'" + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "shared_host_auth_overrides_host_user_protocol_unique": { + "name": "shared_host_auth_overrides_host_user_protocol_unique", + "columns": [ + "host_id", + "user_id", + "protocol" + ], + "isUnique": true + } + }, + "foreignKeys": { + "shared_host_auth_overrides_host_id_ssh_data_id_fk": { + "name": "shared_host_auth_overrides_host_id_ssh_data_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_user_id_users_id_fk": { + "name": "shared_host_auth_overrides_user_id_users_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_auth_overrides", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shared_host_secrets": { + "name": "shared_host_secrets", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "host_access_id": { + "name": "host_access_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_user_id": { + "name": "target_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "protocol": { + "name": "protocol", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ssh'" + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'credential'" + }, + "original_credential_id": { + "name": "original_credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_username": { + "name": "encrypted_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_auth_type": { + "name": "encrypted_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text(16384)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_password": { + "name": "encrypted_key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_key_type": { + "name": "encrypted_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_domain": { + "name": "encrypted_domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_fields": { + "name": "encrypted_fields", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_shared_host_secrets_scope": { + "name": "idx_shared_host_secrets_scope", + "columns": [ + "host_access_id", + "target_user_id", + "protocol" + ], + "isUnique": true + } + }, + "foreignKeys": { + "shared_host_secrets_host_access_id_host_access_id_fk": { + "name": "shared_host_secrets_host_access_id_host_access_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "host_access", + "columnsFrom": [ + "host_access_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_target_user_id_users_id_fk": { + "name": "shared_host_secrets_target_user_id_users_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "users", + "columnsFrom": [ + "target_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_host_secrets_original_credential_id_ssh_credentials_id_fk": { + "name": "shared_host_secrets_original_credential_id_ssh_credentials_id_fk", + "tableFrom": "shared_host_secrets", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "original_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ssh_credential_usage": { + "name": "ssh_credential_usage", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "used_at": { + "name": "used_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_ssh_credential_usage_credential": { + "name": "idx_ssh_credential_usage_credential", + "columns": [ + "credential_id" + ], + "isUnique": false + }, + "idx_ssh_credential_usage_user": { + "name": "idx_ssh_credential_usage_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_credential_usage_credential_id_ssh_credentials_id_fk": { + "name": "ssh_credential_usage_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_host_id_ssh_data_id_fk": { + "name": "ssh_credential_usage_host_id_ssh_data_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ssh_credential_usage_user_id_users_id_fk": { + "name": "ssh_credential_usage_user_id_users_id_fk", + "tableFrom": "ssh_credential_usage", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ssh_credentials": { + "name": "ssh_credentials", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pin": { + "name": "pin", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text(16384)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text(16384)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text(4096)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_password": { + "name": "key_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "detected_key_type": { + "name": "detected_key_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cert_public_key": { + "name": "cert_public_key", + "type": "text(8192)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "usage_count": { + "name": "usage_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_used": { + "name": "last_used", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "shared_source": { + "name": "shared_source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "ssh_credentials_sync_id_unique": { + "name": "ssh_credentials_sync_id_unique", + "columns": [ + "sync_id" + ], + "isUnique": true + }, + "idx_ssh_credentials_user_id": { + "name": "idx_ssh_credentials_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_credentials_user_id_users_id_fk": { + "name": "ssh_credentials_user_id_users_id_fk", + "tableFrom": "ssh_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ssh_folders": { + "name": "ssh_folders", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "local_only": { + "name": "local_only", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "ssh_folders_sync_id_unique": { + "name": "ssh_folders_sync_id_unique", + "columns": [ + "sync_id" + ], + "isUnique": true + }, + "idx_ssh_folders_user_id": { + "name": "idx_ssh_folders_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ssh_folders_user_id_users_id_fk": { + "name": "ssh_folders_user_id_users_id_fk", + "tableFrom": "ssh_folders", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sync_conflicts": { + "name": "sync_conflicts", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "local_row": { + "name": "local_row", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "server_revision": { + "name": "server_revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_sync_conflicts_user": { + "name": "idx_sync_conflicts_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "sync_conflicts_user_id_users_id_fk": { + "name": "sync_conflicts_user_id_users_id_fk", + "tableFrom": "sync_conflicts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sync_link": { + "name": "sync_link", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "server_url": { + "name": "server_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "server_name": { + "name": "server_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "server_version": { + "name": "server_version", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_token": { + "name": "session_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "custom_headers": { + "name": "custom_headers", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "basic_auth": { + "name": "basic_auth", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "allow_invalid_certificate": { + "name": "allow_invalid_certificate", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "remote_user_id": { + "name": "remote_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "remote_username": { + "name": "remote_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "account": { + "name": "account", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "known_types": { + "name": "known_types", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cursor": { + "name": "cursor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'idle'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "linked_at": { + "name": "linked_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "sync_link_user_id_users_id_fk": { + "name": "sync_link_user_id_users_id_fk", + "tableFrom": "sync_link", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sync_records": { + "name": "sync_records", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sync_id": { + "name": "sync_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted": { + "name": "deleted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_hash": { + "name": "error_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_sync_records_user_entity_sync": { + "name": "idx_sync_records_user_entity_sync", + "columns": [ + "user_id", + "entity_type", + "sync_id" + ], + "isUnique": true + }, + "idx_sync_records_user_seq": { + "name": "idx_sync_records_user_seq", + "columns": [ + "user_id", + "seq" + ], + "isUnique": false + } + }, + "foreignKeys": { + "sync_records_user_id_users_id_fk": { + "name": "sync_records_user_id_users_id_fk", + "tableFrom": "sync_records", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "trusted_devices": { + "name": "trusted_devices", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_fingerprint": { + "name": "device_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_info": { + "name": "device_info", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_trusted_devices_user_id": { + "name": "idx_trusted_devices_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "trusted_devices_user_id_users_id_fk": { + "name": "trusted_devices_user_id_users_id_fk", + "tableFrom": "trusted_devices", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ui_preferences": { + "name": "ui_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "ui_preferences_user_id_users_id_fk": { + "name": "ui_preferences_user_id_users_id_fk", + "tableFrom": "ui_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_external_identities": { + "name": "user_external_identities", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_external_identities_provider_subject": { + "name": "idx_user_external_identities_provider_subject", + "columns": [ + "provider_id", + "subject" + ], + "isUnique": true + }, + "idx_user_external_identities_user": { + "name": "idx_user_external_identities_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_external_identities_user_id_users_id_fk": { + "name": "user_external_identities_user_id_users_id_fk", + "tableFrom": "user_external_identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_open_tabs": { + "name": "user_open_tabs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_type": { + "name": "tab_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "host_id": { + "name": "host_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_order": { + "name": "tab_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "backend_session_id": { + "name": "backend_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_open_tabs_user_id": { + "name": "idx_user_open_tabs_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_open_tabs_user_id_users_id_fk": { + "name": "user_open_tabs_user_id_users_id_fk", + "tableFrom": "user_open_tabs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_open_tabs_host_id_ssh_data_id_fk": { + "name": "user_open_tabs_host_id_ssh_data_id_fk", + "tableFrom": "user_open_tabs", + "tableTo": "ssh_data", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_preferences": { + "name": "user_preferences", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "reopen_tabs_on_login": { + "name": "reopen_tabs_on_login", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "font_size": { + "name": "font_size", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "accent_color": { + "name": "accent_color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "storage_mode": { + "name": "storage_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "command_palette_enabled": { + "name": "command_palette_enabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "show_host_tags": { + "name": "show_host_tags", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "host_tray_on_click": { + "name": "host_tray_on_click", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pin_app_rail": { + "name": "pin_app_rail", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expand_app_rail_on_hover": { + "name": "expand_app_rail_on_hover", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "show_pin_app_rail_button": { + "name": "show_pin_app_rail_button", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folders_collapsed": { + "name": "folders_collapsed", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "disable_update_check": { + "name": "disable_update_check", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "confirm_tab_close": { + "name": "confirm_tab_close", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hidden_rail_tabs": { + "name": "hidden_rail_tabs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "compact_host_view": { + "name": "compact_host_view", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_color_scheme": { + "name": "status_color_scheme", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "custom_keybindings": { + "name": "custom_keybindings", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "user_preferences_user_id_users_id_fk": { + "name": "user_preferences_user_id_users_id_fk", + "tableFrom": "user_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_roles": { + "name": "user_roles", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_at": { + "name": "granted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_roles_user_role": { + "name": "idx_user_roles_user_role", + "columns": [ + "user_id", + "role_id" + ], + "isUnique": true + }, + "idx_user_roles_role_id": { + "name": "idx_user_roles_role_id", + "columns": [ + "role_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_granted_by_users_id_fk": { + "name": "user_roles_granted_by_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_second_factors": { + "name": "user_second_factors", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "factor_id": { + "name": "factor_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enrolled_at": { + "name": "enrolled_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_user_second_factors_user_factor": { + "name": "idx_user_second_factors_user_factor", + "columns": [ + "user_id", + "plugin_id", + "factor_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "user_second_factors_user_id_users_id_fk": { + "name": "user_second_factors_user_id_users_id_fk", + "tableFrom": "user_second_factors", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_admin": { + "name": "is_admin", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "registered_at": { + "name": "registered_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "donation_modal_dismissed": { + "name": "donation_modal_dismissed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/sqlite/meta/_journal.json b/drizzle/sqlite/meta/_journal.json index 5291f32be..e31c0f7f6 100644 --- a/drizzle/sqlite/meta/_journal.json +++ b/drizzle/sqlite/meta/_journal.json @@ -428,6 +428,20 @@ "when": 1790742085632, "tag": "0060_fine_cerise", "breakpoints": true + }, + { + "idx": 61, + "version": "6", + "when": 1791209294736, + "tag": "0061_retire_2_8_columns", + "breakpoints": true + }, + { + "idx": 62, + "version": "6", + "when": 1791209309626, + "tag": "0062_external_session_ref", + "breakpoints": true } ] } \ No newline at end of file diff --git a/electron/preload.js b/electron/preload.js index 4835dd294..d7b88ad3f 100644 --- a/electron/preload.js +++ b/electron/preload.js @@ -83,9 +83,6 @@ contextBridge.exposeInMainWorld("electronAPI", { externalBrowserLogin: (authUrl, callbackPort) => ipcRenderer.invoke("external-browser-login", authUrl, callbackPort), - // 2.8 name, read by older server login pages. Remove in 26.10.0. - oidcSystemBrowserAuth: (authUrl, callbackPort) => - ipcRenderer.invoke("external-browser-login", authUrl, callbackPort), openExternalEditor: (fileData) => ipcRenderer.invoke("open-external-editor", fileData), diff --git a/packages/plugin-sdk/cli/index.mjs b/packages/plugin-sdk/cli/index.mjs index fbef5dd43..eef9063f8 100755 --- a/packages/plugin-sdk/cli/index.mjs +++ b/packages/plugin-sdk/cli/index.mjs @@ -18,7 +18,11 @@ const COMMANDS = { build, patch: async ({ cwd }) => applyPatches(cwd, readManifest(cwd).id), validate, - test, + // The tests exercise patched dependencies, so patch first like build does. + test: async (options) => { + applyPatches(options.cwd, readManifest(options.cwd).id); + return test(options); + }, pack, sign, verify, @@ -36,7 +40,7 @@ if (!command || command === "--help" || command === "-h") { " build Bundle the plugin into dist/", " patch Apply the plugin's dependency patches (build does this too)", " validate Check manifest.json and the files it names", - " test Run the plugin's vitest suite", + " test Apply the patches, then run the plugin's vitest suite", " pack Write -.tmxplug of the built plugin [--out dir]", " sign Sign a .tmxplug with TERMIX_PLUGIN_SIGNING_KEY ", " verify Check a .tmxplug's .sig --key [,...]", diff --git a/packages/plugin-sdk/src/backend.ts b/packages/plugin-sdk/src/backend.ts index 660bad272..92e153caa 100644 --- a/packages/plugin-sdk/src/backend.ts +++ b/packages/plugin-sdk/src/backend.ts @@ -373,7 +373,7 @@ export interface PluginHttp { /** * The public base URL a request came in on: origin plus the install's base * path, no trailing slash. Honours forwarded headers from a trusted proxy, - * BASE_PATH and OIDC_FORCE_HTTPS. For building absolute callback URLs. + * BASE_PATH and EXTERNAL_FORCE_HTTPS. For building absolute callback URLs. */ baseUrl: (req: unknown) => string; } @@ -1368,10 +1368,9 @@ export type PluginVerifiedIdentity = sid?: string | null; }; /** - * The values a 2.8 install kept on the user row (`oidc_identifier`, - * `sso_provider_id`). Only for a plugin that took over a 2.8 login - * method: core finds pre-2.9 accounts by `identifier` and keeps writing - * both for new users so a downgrade still works. + * The identifier a 2.8 install kept for the user. Only for a plugin + * that took over a 2.8 login method: core also checks `identifier` + * against `allowedUsers`. `providerRowId` is no longer read. */ legacy?: { identifier: string; providerRowId?: number | null }; /** diff --git a/packages/plugin-sdk/src/manifest.ts b/packages/plugin-sdk/src/manifest.ts index a632346ab..351839ed3 100644 --- a/packages/plugin-sdk/src/manifest.ts +++ b/packages/plugin-sdk/src/manifest.ts @@ -275,11 +275,6 @@ export interface PluginSettingsField { group?: string; /** Registered component id. Required when type is "custom". */ component?: string; - /** - * Deprecated and ignored: host defaults cover every host field now. Still - * accepted so an older manifest loads. Removed in 26.10.0. - */ - defaultFrom?: string; /** * Host scope only: whether the field can have a host default (server, * user or folder) that hosts follow until they set their own. On by @@ -644,7 +639,6 @@ const ALLOWED_SETTINGS_FIELD = [ "group", "component", "hidden", - "defaultFrom", "defaultable", "defaultLevels", "personal", @@ -1614,9 +1608,6 @@ function validateSettings(settings: unknown, errors: string[]): void { host.fields.forEach((raw, index) => { if (!isPlainObject(raw)) return; const fieldAt = `${at}.fields[${index}]`; - if ("defaultFrom" in raw && typeof raw.defaultFrom !== "string") { - errors.push(`${fieldAt}.defaultFrom must be a string`); - } for (const flag of ["defaultable", "personal"]) { if (flag in raw && typeof raw[flag] !== "boolean") { errors.push(`${fieldAt}.${flag} must be a boolean`); @@ -1670,7 +1661,6 @@ function validateSettings(settings: unknown, errors: string[]): void { fields.forEach((raw, index) => { if (!isPlainObject(raw)) return; for (const key of [ - "defaultFrom", "defaultable", "defaultLevels", "personal", diff --git a/scripts/plugin-cli-bootstrap.test.ts b/scripts/plugin-cli-bootstrap.test.ts index d88b19b37..5be8186a5 100644 --- a/scripts/plugin-cli-bootstrap.test.ts +++ b/scripts/plugin-cli-bootstrap.test.ts @@ -59,3 +59,18 @@ describe("plugin CLI before the SDK is built", () => { expect(result.stderr).not.toContain("ERR_MODULE_NOT_FOUND"); }); }); + +describe("termix-plugin test", () => { + it("applies the plugin's patches before running vitest", () => { + const { cli, plugin, run } = fixture(); + fs.mkdirSync(path.join(plugin, "patches")); + fs.writeFileSync( + path.join(plugin, "patches", "mark.cjs"), + 'require("node:fs").writeFileSync("patched.txt", "yes");', + ); + run(cli, "test"); + expect(fs.readFileSync(path.join(plugin, "patched.txt"), "utf8")).toBe( + "yes", + ); + }); +}); diff --git a/src/backend/auth/builtin-login-methods.ts b/src/backend/auth/builtin-login-methods.ts index 1cf2181b2..28630b5f2 100644 --- a/src/backend/auth/builtin-login-methods.ts +++ b/src/backend/auth/builtin-login-methods.ts @@ -69,8 +69,11 @@ export async function verifyPasswordLogin(request: { throw new LoginMethodError("Invalid username or password", 401); } - if (user.isOidc && (!user.passwordHash || user.passwordHash.trim() === "")) { - authLogger.warn("OIDC-only user attempted traditional login", { + if ( + user.isExternal && + (!user.passwordHash || user.passwordHash.trim() === "") + ) { + authLogger.warn("External-only user attempted password login", { operation: "user_login", username, userId: user.id, diff --git a/src/backend/auth/external-account.ts b/src/backend/auth/external-account.ts index e92c515a7..03b82e509 100644 --- a/src/backend/auth/external-account.ts +++ b/src/backend/auth/external-account.ts @@ -1,10 +1,10 @@ /** * Whether a user signs in through an external login (SSO, LDAP or any login - * plugin) rather than only a password. Read from the is_oidc column, which - * keeps its 2.8 name until 26.10.0 replaces it with user_external_identities. + * plugin) rather than only a password: they have a user_external_identities + * row. UserRepository fills isExternal. */ export function isExternalAccount(user: { - isOidc?: boolean | number | null; + isExternal?: boolean | null; }): boolean { - return !!user.isOidc; + return !!user.isExternal; } diff --git a/src/backend/auth/login-pipeline.ts b/src/backend/auth/login-pipeline.ts index c3e5a3655..4dcde3e84 100644 --- a/src/backend/auth/login-pipeline.ts +++ b/src/backend/auth/login-pipeline.ts @@ -175,7 +175,7 @@ async function unlockUser( ): Promise { const authManager = AuthManager.getInstance(); - if (identity.kind === "external" || user.isOidc) { + if (identity.kind === "external" || user.isExternal) { try { await authManager.authenticateExternalUser(user.id, deviceType); } catch (error) { @@ -280,11 +280,7 @@ export async function runLogin( ); } - const ssoClaims = { - ssoProviderId: identity.ssoProviderId ?? null, - oidcSub: identity.oidcSub ?? null, - oidcSid: identity.oidcSid ?? null, - }; + const ssoClaims = { externalSession: identity.externalSession ?? null }; if ( factors.required.length > 0 && @@ -499,9 +495,7 @@ export async function verifySecondFactorAndRespond( const session = await issueSession(req, user, { methodId: pending?.methodId ?? "password", rememberMe, - ssoProviderId: pending?.ssoProviderId ?? null, - oidcSub: pending?.oidcSub ?? null, - oidcSid: pending?.oidcSid ?? null, + externalSession: pending?.externalSession ?? null, }); consumePendingLogin(lookup.token); diff --git a/src/backend/auth/provisioning.ts b/src/backend/auth/provisioning.ts index e071667a4..9a9089b8b 100644 --- a/src/backend/auth/provisioning.ts +++ b/src/backend/auth/provisioning.ts @@ -30,12 +30,15 @@ type ExternalIdentity = Extract; const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000; const ONE_DAY_MS = 24 * 60 * 60 * 1000; -/** SSO auto-provisioning: the admin setting, or OIDC_ALLOW_REGISTRATION. */ +/** The admin setting that turns external auto-provisioning on. */ +export const EXTERNAL_AUTO_PROVISION_KEY = "external_auto_provision"; + +/** SSO auto-provisioning: the admin setting, or EXTERNAL_ALLOW_REGISTRATION. */ async function isExternalProvisioningAllowed(): Promise { try { if ( await createCurrentSettingsRepository().getBoolean( - "oidc_auto_provision", + EXTERNAL_AUTO_PROVISION_KEY, false, ) ) { @@ -44,9 +47,12 @@ async function isExternalProvisioningAllowed(): Promise { } catch { // fall through to the environment } - return ( - (process.env.OIDC_ALLOW_REGISTRATION || "").trim().toLowerCase() === "true" - ); + // OIDC_ALLOW_REGISTRATION is the 2.9 name, still read for one release. + const allow = + process.env.EXTERNAL_ALLOW_REGISTRATION ?? + process.env.OIDC_ALLOW_REGISTRATION ?? + ""; + return allow.trim().toLowerCase() === "true"; } /** @@ -150,74 +156,12 @@ async function applyRoleSync( async function findLinkedUser( identity: ExternalIdentity, ): Promise { - const identities = createCurrentUserAuthRepository(); - const users = createCurrentUserRepository(); - - const linked = await identities.findIdentity( + const linked = await createCurrentUserAuthRepository().findIdentity( identity.provider, identity.subject, ); - if (linked) { - const user = await users.findById(linked.userId); - if (user) return user; - } - - // Accounts from before user_external_identities matched on the raw - // identifier column; link them the first time they sign in. The column is - // not scoped to a provider, so only an SSO account that has no link yet, - // from the same provider when both say which, can be claimed this way. - if (identity.legacyIdentifier) { - const legacy = await users.findByExternalIdentifier( - identity.legacyIdentifier, - ); - if (legacy && (await mayClaimLegacyAccount(legacy, identity))) { - await identities.linkIdentity({ - userId: legacy.id, - providerId: identity.provider, - subject: identity.subject, - email: identity.email, - }); - return legacy; - } - } - return null; -} - -async function mayClaimLegacyAccount( - legacy: UserRecord, - identity: ExternalIdentity, -): Promise { - if (!legacy.isOidc) return false; - if ( - legacy.ssoProviderId != null && - identity.ssoProviderId != null && - legacy.ssoProviderId !== identity.ssoProviderId - ) { - return false; - } - const links = await createCurrentUserAuthRepository().listIdentitiesForUser( - legacy.id, - ); - // An unscoped legacy subject may already have been migrated under a - // different provider. Creating another account loses access to the old - // data; claiming it automatically could cross an identity-provider boundary. - if ( - legacy.ssoProviderId == null && - identity.ssoProviderId != null && - identity.legacyIdentifier === identity.subject && - links.some( - (link) => - link.subject === identity.subject && - link.providerId !== identity.provider, - ) - ) { - throw new LoginMethodError( - "A legacy SSO account has an unresolved provider mapping. Ask an administrator to verify and repair its identity mapping before signing in.", - 409, - "legacy_identity_conflict", - ); - } - return links.length === 0; + if (!linked) return null; + return createCurrentUserRepository().findById(linked.userId); } /** @@ -249,10 +193,6 @@ async function createUser( username: await freeUsername(identity.name || identity.subject, null), passwordHash: "", isAdmin: !!identity.isAdmin, - isOidc: true, - oidcIdentifier: - identity.legacyIdentifier ?? `${identity.provider}:${identity.subject}`, - ssoProviderId: identity.ssoProviderId ?? null, }); try { diff --git a/src/backend/auth/session-issuer.ts b/src/backend/auth/session-issuer.ts index f8b978bba..17aeb3c81 100644 --- a/src/backend/auth/session-issuer.ts +++ b/src/backend/auth/session-issuer.ts @@ -17,6 +17,7 @@ import { createCurrentUserAuthRepository, } from "../database/repositories/factory.js"; import type { UserRecord } from "../database/repositories/user-repository.js"; +import type { ExternalSessionRef } from "./types.js"; const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000; @@ -64,9 +65,7 @@ export interface IssueSessionOptions { rememberMe: boolean; /** Username the rate limiter counted attempts under, to clear them. */ rateLimitUsername?: string; - ssoProviderId?: number | null; - oidcSub?: string | null; - oidcSid?: string | null; + externalSession?: ExternalSessionRef | null; /** SSO logins keep desktop and mobile apps signed in for 30 days. */ longLivedForApps?: boolean; } @@ -93,9 +92,7 @@ export async function issueSession( rememberMe: options.rememberMe, deviceType: deviceInfo.type, deviceInfo: deviceInfo.deviceInfo, - oidcSub: options.oidcSub ?? null, - oidcSid: options.oidcSid ?? null, - ssoProviderId: options.ssoProviderId ?? null, + externalSession: options.externalSession ?? null, }); if (options.rateLimitUsername) { @@ -141,8 +138,6 @@ export async function issueSession( username: user.username, userId: user.id, is_external: isExternalAccount(user), - // 2.8 name, kept until 26.10.0. - is_oidc: !!user.isOidc, // Any second factor; the name is what 2.8 clients read. totp_enabled: await createCurrentUserAuthRepository().hasSecondFactor( user.id, diff --git a/src/backend/auth/types.ts b/src/backend/auth/types.ts index a618a305c..8f803795a 100644 --- a/src/backend/auth/types.ts +++ b/src/backend/auth/types.ts @@ -2,19 +2,23 @@ import type { PluginVerifiedIdentity } from "@termix/plugin-sdk/backend"; export { LoginMethodError } from "@termix/plugin-sdk/backend"; +/** The logout claims a login carried, stored as sessions.external_session_ref. */ +export interface ExternalSessionRef { + providerId?: number | null; + sub?: string | null; + sid?: string | null; +} + /** Extra fields core's own methods may set. Plugins never see these. */ interface CoreIdentityExtras { /** - * The identifier string the user row carried before user_external_identities - * existed ("ldap::", "github::" or a bare OIDC - * subject). Found users get their identity row linked lazily; new users keep - * the column filled so older code that reads it still works. + * The identifier a 2.8 install kept for the user ("ldap::", + * "github::" or a bare OIDC subject). Only checked against + * the allowed-users list. */ legacyIdentifier?: string; - /** SSO provider row the session came from, for back-channel logout. */ - ssoProviderId?: number | null; - oidcSub?: string | null; - oidcSid?: string | null; + /** Kept on the session for an identity provider's back-channel logout. */ + externalSession?: ExternalSessionRef | null; /** Provider group to role mapping, applied on every login. */ roleSync?: { desired: string[]; managed: string[] }; /** Shown when the user's data key cannot be unlocked by this method. */ @@ -30,8 +34,6 @@ export interface PendingLogin { userId: string; methodId: string; rememberMe: boolean; - ssoProviderId?: number | null; - oidcSub?: string | null; - oidcSid?: string | null; + externalSession?: ExternalSessionRef | null; createdAt: number; } diff --git a/src/backend/boot.ts b/src/backend/boot.ts index fdf207424..fffb5790d 100644 --- a/src/backend/boot.ts +++ b/src/backend/boot.ts @@ -65,15 +65,7 @@ export async function runCoreBootMigrations(): Promise { await import("./utils/crypto-migration/private-shared-ssh-auth-migration.js"); await runPrivateSharedSshAuthMigration(); - const { runExternalIdentityMigration } = - await import("./upgrade/external-identity-migration.js"); - await runExternalIdentityMigration(); - - const { runHostStatusConfigMigration } = - await import("./utils/crypto-migration/host-status-config-migration.js"); - await runHostStatusConfigMigration(); - - const { runSshOptionsMigration } = - await import("./utils/crypto-migration/ssh-options-migration.js"); - await runSshOptionsMigration(); + const { runExternalAutoProvisionMigration } = + await import("./upgrade/external-auto-provision-migration.js"); + await runExternalAutoProvisionMigration(); } diff --git a/src/backend/database/database.ts b/src/backend/database/database.ts index 7011ceaf1..f5e071f5f 100644 --- a/src/backend/database/database.ts +++ b/src/backend/database/database.ts @@ -630,15 +630,15 @@ app.post("/database/export", authenticateJWT, async (req, res) => { return res.status(404).json({ error: "User not found" }); } - const isOidcUser = !!user.isOidc; + const isExternalUser = !!user.isExternal; if (!DataCrypto.getUserDataKey(userId)) { - if (isOidcUser) { - const oidcUnlocked = await authManager.authenticateExternalUser( + if (isExternalUser) { + const externalUnlocked = await authManager.authenticateExternalUser( userId, deviceInfo.type, ); - if (!oidcUnlocked) { + if (!externalUnlocked) { return res.status(403).json({ error: "Failed to unlock user data with SSO credentials", }); @@ -699,17 +699,7 @@ app.post("/database/export", authenticateJWT, async (req, res) => { id TEXT PRIMARY KEY, username TEXT NOT NULL, password_hash TEXT NOT NULL, - is_admin INTEGER NOT NULL DEFAULT 0, - is_oidc INTEGER NOT NULL DEFAULT 0, - oidc_identifier TEXT, - client_id TEXT, - client_secret TEXT, - issuer_url TEXT, - authorization_url TEXT, - token_url TEXT, - identifier_path TEXT, - name_path TEXT, - scopes TEXT DEFAULT 'openid email profile' + is_admin INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE settings ( @@ -740,9 +730,7 @@ app.post("/database/export", authenticateJWT, async (req, res) => { jump_hosts TEXT, status_check_enabled INTEGER NOT NULL DEFAULT 1, status_check_interval INTEGER, - terminal_config TEXT, ssh_options TEXT, - quick_actions TEXT, notes TEXT, use_socks5 INTEGER, socks5_host TEXT, @@ -805,31 +793,21 @@ app.post("/database/export", authenticateJWT, async (req, res) => { const userRecord = user; const insertUser = exportDb.prepare(` - INSERT INTO users (id, username, password_hash, is_admin, is_oidc, oidc_identifier, client_id, client_secret, issuer_url, authorization_url, token_url, identifier_path, name_path, scopes) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + INSERT INTO users (id, username, password_hash, is_admin) + VALUES (?, ?, ?, ?) `); insertUser.run( userRecord.id, userRecord.username, "[EXPORTED_USER_NO_PASSWORD]", userRecord.isAdmin ? 1 : 0, - userRecord.isOidc ? 1 : 0, - userRecord.oidcIdentifier || null, - userRecord.clientId || null, - userRecord.clientSecret || null, - userRecord.issuerUrl || null, - userRecord.authorizationUrl || null, - userRecord.tokenUrl || null, - userRecord.identifierPath || null, - userRecord.namePath || null, - userRecord.scopes || null, ); const sshHosts = await createCurrentHostRepository().listDecryptedByUserId(userId); const insertHost = exportDb.prepare(` - INSERT INTO ssh_data (id, user_id, connection_type, name, ip, port, username, folder, tags, pin, auth_type, force_keyboard_interactive, password, key, key_password, key_type, sudo_password, credential_id, override_credential_username, jump_hosts, status_check_enabled, status_check_interval, terminal_config, ssh_options, quick_actions, notes, use_socks5, socks5_host, socks5_port, socks5_username, socks5_password, socks5_proxy_chain, port_knock_sequence, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + INSERT INTO ssh_data (id, user_id, connection_type, name, ip, port, username, folder, tags, pin, auth_type, force_keyboard_interactive, password, key, key_password, key_type, sudo_password, credential_id, override_credential_username, jump_hosts, status_check_enabled, status_check_interval, ssh_options, notes, use_socks5, socks5_host, socks5_port, socks5_username, socks5_password, socks5_proxy_chain, port_knock_sequence, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `); for (const decrypted of sshHosts) { @@ -856,9 +834,7 @@ app.post("/database/export", authenticateJWT, async (req, res) => { decrypted.jumpHosts || null, decrypted.statusCheckEnabled === false ? 0 : 1, decrypted.statusCheckInterval ?? null, - decrypted.terminalConfig || null, decrypted.sshOptions || null, - decrypted.quickActions || null, decrypted.notes || null, decrypted.useSocks5 ? 1 : 0, decrypted.socks5Host || null, @@ -1074,15 +1050,15 @@ app.post( return res.status(404).json({ error: "User not found" }); } - const isOidcUser = !!userRecord.isOidc; + const isExternalUser = !!userRecord.isExternal; if (!DataCrypto.getUserDataKey(userId)) { - if (isOidcUser) { - const oidcUnlocked = await authManager.authenticateExternalUser( + if (isExternalUser) { + const externalUnlocked = await authManager.authenticateExternalUser( userId, deviceInfo.type, ); - if (!oidcUnlocked) { + if (!externalUnlocked) { return res.status(403).json({ error: "Failed to unlock user data with SSO credentials", }); @@ -1198,7 +1174,6 @@ app.post( ), jumpHosts: host.jump_hosts, ...legacyStatusCheck(host), - terminalConfig: host.terminal_config, // Exports from before 2.9.0 carry these in terminal_config. sshOptions: host.ssh_options ?? @@ -1206,7 +1181,6 @@ app.post( terminalConfig: host.terminal_config, }) ?? null, - quickActions: host.quick_actions, notes: host.notes, useSocks5: Boolean(host.use_socks5), socks5Host: host.socks5_host, diff --git a/src/backend/database/db/index.ts b/src/backend/database/db/index.ts index 509545c89..8da9cb393 100644 --- a/src/backend/database/db/index.ts +++ b/src/backend/database/db/index.ts @@ -24,6 +24,16 @@ import { resolveDatabaseDialect, type DatabaseDialect } from "./dialect.js"; import { SYSTEM_ROLE_DEFAULTS } from "../../utils/permission-catalog.js"; import { connectRemoteDatabase } from "./connect.js"; import { runRemoteMigrations } from "./migrate.js"; +import { assertNotPre29Database } from "./upgrade-guard.js"; +import { + LEGACY_USERNAME_PLACEHOLDER_UPDATES, + legacySudoMoves, + RETIRED_COLUMNS, + RETIRED_SETTINGS, + REMOTE_RETIRED_COLUMNS, + type LegacySudoRow, +} from "./retired-columns.js"; +import { sql, type SQL } from "drizzle-orm"; import type { PortableDatabase } from "../repositories/database-context.js"; const dataDir = process.env.DATA_DIR || "./db/data"; @@ -217,22 +227,21 @@ async function initializeCompleteDatabase(): Promise { db = drizzle(sqlite, { schema }); + await assertNotPre29Database( + (name) => + !!sqlite + .prepare( + "SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?", + ) + .get(name), + ); + sqlite.exec(` CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, username TEXT NOT NULL, password_hash TEXT NOT NULL, is_admin INTEGER NOT NULL DEFAULT 0, - is_oidc INTEGER NOT NULL DEFAULT 0, - oidc_identifier TEXT, - client_id TEXT, - client_secret TEXT, - issuer_url TEXT, - authorization_url TEXT, - token_url TEXT, - identifier_path TEXT, - name_path TEXT, - scopes TEXT DEFAULT 'openid email profile', registered_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, donation_modal_dismissed INTEGER NOT NULL DEFAULT 0 ); @@ -308,7 +317,6 @@ async function initializeCompleteDatabase(): Promise { force_keyboard_interactive TEXT, status_check_enabled INTEGER NOT NULL DEFAULT 1, status_check_interval INTEGER, - terminal_config TEXT, notes TEXT, use_socks5 INTEGER, socks5_host TEXT, @@ -357,12 +365,10 @@ async function initializeCompleteDatabase(): Promise { name TEXT NOT NULL, color TEXT, icon TEXT, - credential_id INTEGER, sort_order INTEGER, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE, - FOREIGN KEY (credential_id) REFERENCES ssh_credentials (id) ON DELETE SET NULL + FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS recent_activity ( @@ -672,6 +678,14 @@ async function initializeCompleteDatabase(): Promise { }); } + // 2.9.1 added this fix, so a 2.9.0 install has not run it yet. It reads the + // column migrateSchema drops. + if (columnNames("users").includes("oidc_identifier")) { + const { runSsoLegacyIdentityMigration } = + await import("../../upgrade/sso-legacy-identity-migration.js"); + await runSsoLegacyIdentityMigration(); + } + migrateSchema(); vacuumIfFreelistBloated(); @@ -782,6 +796,135 @@ const relaxPluginGrantGrantedBy = () => { } }; +const columnNames = (table: string): string[] => + ( + sqlite.prepare(`PRAGMA table_info(${table})`).all() as Array<{ + name: string; + }> + ).map((col) => col.name); + +/** + * ssh_folders.credential_id became the folder's auth default. It carries a + * foreign key, which ALTER TABLE DROP COLUMN refuses, so the table is rebuilt. + */ +const dropFolderCredentialColumn = () => { + const columns = columnNames("ssh_folders"); + if (!columns.includes("credential_id")) return; + + const kept = [ + "id", + "user_id", + "name", + "color", + "icon", + "sort_order", + "sync_id", + "local_only", + "created_at", + "updated_at", + ].filter((name) => columns.includes(name)); + const list = kept.map((name) => `"${name}"`).join(", "); + + sqlite.exec("PRAGMA foreign_keys = OFF"); + try { + sqlite.exec(` + BEGIN; + CREATE TABLE ssh_folders_new ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id TEXT NOT NULL, + name TEXT NOT NULL, + color TEXT, + icon TEXT, + sort_order INTEGER, + sync_id TEXT, + local_only INTEGER NOT NULL DEFAULT 0, + created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE + ); + INSERT INTO ssh_folders_new (${list}) SELECT ${list} FROM ssh_folders; + DROP TABLE ssh_folders; + ALTER TABLE ssh_folders_new RENAME TO ssh_folders; + CREATE UNIQUE INDEX IF NOT EXISTS idx_ssh_folders_sync_id ON ssh_folders(sync_id); + COMMIT; + `); + } catch (error) { + if (sqlite.inTransaction) sqlite.exec("ROLLBACK"); + databaseLogger.warn("Failed to drop ssh_folders.credential_id", { + operation: "schema_migration", + error: getErrorMessage(error), + }); + } finally { + sqlite.exec("PRAGMA foreign_keys = ON"); + } +}; + +const moveLegacySudoPasswords = () => { + if (!columnNames("ssh_data").includes("terminal_config")) return; + try { + const rows = sqlite + .prepare( + "SELECT id, terminal_config, sudo_password FROM ssh_data WHERE terminal_config LIKE '%sudoPassword%'", + ) + .all() as LegacySudoRow[]; + const update = sqlite.prepare( + "UPDATE ssh_data SET sudo_password = ? WHERE id = ?", + ); + for (const move of legacySudoMoves(rows)) { + update.run(move.sudoPassword, move.id); + } + } catch (error) { + databaseLogger.warn("Failed to move legacy sudo passwords", { + operation: "schema_migration", + error: getErrorMessage(error), + }); + } +}; + +const dropRetiredColumns = () => { + moveLegacySudoPasswords(); + for (const [table, retired] of Object.entries(RETIRED_COLUMNS)) { + const columns = new Set(columnNames(table)); + for (const column of retired) { + if (!columns.has(column)) continue; + try { + sqlite.exec(`ALTER TABLE ${table} DROP COLUMN "${column}"`); + } catch (error) { + databaseLogger.warn(`Failed to drop ${table}.${column}`, { + operation: "schema_migration", + error: getErrorMessage(error), + }); + } + } + } + dropFolderCredentialColumn(); + + // 2.8 spelled $external.username as $oidc.preferred_username. + for (const statement of LEGACY_USERNAME_PLACEHOLDER_UPDATES) { + try { + sqlite.exec(statement); + } catch (error) { + databaseLogger.warn("Failed to rename a legacy username placeholder", { + operation: "schema_migration", + error: getErrorMessage(error), + }); + } + } + + try { + sqlite + .prepare( + `DELETE FROM settings WHERE key IN (${RETIRED_SETTINGS.map(() => "?").join(", ")})`, + ) + .run(...RETIRED_SETTINGS); + } catch (error) { + databaseLogger.warn("Failed to delete retired settings", { + operation: "schema_migration", + error: getErrorMessage(error), + }); + } +}; + const migrateSchema = () => { repairSnippetsNoteColumn(sqlite); addColumnIfNotExists("user_preferences", "theme", "TEXT"); @@ -789,7 +932,6 @@ const migrateSchema = () => { addColumnIfNotExists("user_preferences", "accent_color", "TEXT"); addColumnIfNotExists("user_preferences", "language", "TEXT"); addColumnIfNotExists("user_preferences", "storage_mode", "TEXT"); - addColumnIfNotExists("user_preferences", "command_autocomplete", "INTEGER"); addColumnIfNotExists("user_preferences", "command_palette_enabled", "INTEGER"); addColumnIfNotExists("user_preferences", "show_host_tags", "INTEGER"); addColumnIfNotExists("user_preferences", "host_tray_on_click", "INTEGER"); @@ -805,31 +947,15 @@ const migrateSchema = () => { "INTEGER", ); addColumnIfNotExists("user_preferences", "folders_collapsed", "INTEGER"); - addColumnIfNotExists("user_preferences", "confirm_snippet_execution", "INTEGER"); addColumnIfNotExists("user_preferences", "disable_update_check", "INTEGER"); addColumnIfNotExists("user_preferences", "confirm_tab_close", "INTEGER"); addColumnIfNotExists("user_preferences", "hidden_rail_tabs", "TEXT"); addColumnIfNotExists("user_preferences", "compact_host_view", "INTEGER"); addColumnIfNotExists("user_preferences", "status_color_scheme", "TEXT"); - addColumnIfNotExists("user_preferences", "custom_themes", "TEXT"); addColumnIfNotExists("user_preferences", "custom_keybindings", "TEXT"); - addColumnIfNotExists("user_preferences", "terminal_defaults", "TEXT"); - addColumnIfNotExists("user_preferences", "terminal_macros", "TEXT"); addColumnIfNotExists("users", "is_admin", "INTEGER NOT NULL DEFAULT 0"); - addColumnIfNotExists("users", "is_oidc", "INTEGER NOT NULL DEFAULT 0"); - addColumnIfNotExists("users", "oidc_identifier", "TEXT"); - addColumnIfNotExists("users", "client_id", "TEXT"); - addColumnIfNotExists("users", "client_secret", "TEXT"); - addColumnIfNotExists("users", "issuer_url", "TEXT"); - addColumnIfNotExists("users", "authorization_url", "TEXT"); - addColumnIfNotExists("users", "token_url", "TEXT"); - - addColumnIfNotExists("users", "identifier_path", "TEXT"); - addColumnIfNotExists("users", "name_path", "TEXT"); - addColumnIfNotExists("users", "scopes", "TEXT"); - const hadRegisteredAtColumn = (() => { try { sqlite.prepare(`SELECT "registered_at" FROM users LIMIT 1`).get(); @@ -878,9 +1004,7 @@ const migrateSchema = () => { "INTEGER NOT NULL DEFAULT 0", ); - addColumnIfNotExists("sessions", "oidc_sub", "TEXT"); - addColumnIfNotExists("sessions", "oidc_sid", "TEXT"); - addColumnIfNotExists("sessions", "sso_provider_id", "INTEGER"); + addColumnIfNotExists("sessions", "external_session_ref", "TEXT"); addColumnIfNotExists("ssh_data", "name", "TEXT"); addColumnIfNotExists("ssh_data", "folder", "TEXT"); @@ -926,8 +1050,6 @@ const migrateSchema = () => { "INTEGER NOT NULL DEFAULT 1", ); addColumnIfNotExists("ssh_data", "status_check_interval", "INTEGER"); - addColumnIfNotExists("ssh_data", "terminal_config", "TEXT"); - addColumnIfNotExists("ssh_data", "quick_actions", "TEXT"); addColumnIfNotExists("ssh_data", "connection_type", 'TEXT NOT NULL DEFAULT "ssh"'); addColumnIfNotExists("ssh_data", "domain", "TEXT"); @@ -1145,19 +1267,6 @@ const migrateSchema = () => { }); } - try { - sqlite.prepare("SELECT credential_id FROM ssh_folders LIMIT 1").get(); - } catch { - try { - sqlite.exec("ALTER TABLE ssh_folders ADD COLUMN credential_id INTEGER REFERENCES ssh_credentials(id) ON DELETE SET NULL"); - } catch (alterError) { - databaseLogger.warn("Failed to add credential_id column to ssh_folders", { - operation: "schema_migration", - error: alterError, - }); - } - } - try { sqlite.prepare("SELECT sudo_password FROM ssh_data LIMIT 1").get(); } catch { @@ -1177,7 +1286,6 @@ const migrateSchema = () => { { column: "override_credential_username", sql: "ALTER TABLE ssh_data ADD COLUMN override_credential_username INTEGER" }, { column: "share_ssh_auth", sql: "ALTER TABLE ssh_data ADD COLUMN share_ssh_auth INTEGER NOT NULL DEFAULT 0" }, { column: "jump_hosts", sql: "ALTER TABLE ssh_data ADD COLUMN jump_hosts TEXT" }, - { column: "quick_actions", sql: "ALTER TABLE ssh_data ADD COLUMN quick_actions TEXT" }, { column: "socks5_proxy_chain", sql: "ALTER TABLE ssh_data ADD COLUMN socks5_proxy_chain TEXT" }, { column: "host_key_fingerprint", sql: "ALTER TABLE ssh_data ADD COLUMN host_key_fingerprint TEXT" }, { column: "host_key_type", sql: "ALTER TABLE ssh_data ADD COLUMN host_key_type TEXT" }, @@ -1459,76 +1567,6 @@ const migrateSchema = () => { }); } - addColumnIfNotExists("users", "sso_provider_id", "INTEGER"); - - try { - const usersTableInfo = sqlite.prepare("PRAGMA table_info(users)").all() as Array<{ - cid: number; - name: string; - type: string; - notnull: number; - dflt_value: string | null; - pk: number; - }>; - const legacyNotNullColumns = new Set([ - "client_id", - "client_secret", - "issuer_url", - "authorization_url", - "token_url", - "identifier_path", - "name_path", - "scopes", - ]); - const hasStaleNotNull = usersTableInfo.some( - (col) => legacyNotNullColumns.has(col.name) && col.notnull === 1, - ); - - if (hasStaleNotNull) { - const tempTableName = "users_temp_migration"; - const columnDefs = usersTableInfo - .map((col) => { - const parts = [`"${col.name}"`, col.type || "TEXT"]; - if (col.pk === 1) parts.push("PRIMARY KEY"); - if (col.notnull === 1 && !legacyNotNullColumns.has(col.name)) { - parts.push("NOT NULL"); - } - if (col.dflt_value !== null) { - parts.push(`DEFAULT ${col.dflt_value}`); - } - return parts.join(" "); - }) - .join(",\n "); - const allColumns = usersTableInfo.map((col) => `"${col.name}"`).join(", "); - - sqlite.exec(`PRAGMA foreign_keys = OFF`); - sqlite.exec(` - CREATE TABLE ${tempTableName} ( - ${columnDefs} - ); - - INSERT INTO ${tempTableName} SELECT ${allColumns} FROM users; - - DROP TABLE users; - - ALTER TABLE ${tempTableName} RENAME TO users; - `); - sqlite.exec(`PRAGMA foreign_keys = ON`); - - databaseLogger.info( - "Successfully migrated users table to remove legacy OIDC NOT NULL constraints", - { - operation: "schema_migration_users_oidc_nullable", - }, - ); - } - } catch (migrationError) { - databaseLogger.warn("Failed to migrate users table legacy OIDC columns", { - operation: "schema_migration", - error: migrationError, - }); - } - // --- alerts begin --- // alert_rules, alert_rule_channels and alert_firings were only ever created // here, never declared in schema.ts, so they never existed on Postgres or @@ -1686,6 +1724,8 @@ const migrateSchema = () => { // they referenced, which defeats the point of keeping them. migrateAuditRetention(sqlite); + dropRetiredColumns(); + // Runs last so every table and column added above already exists. createPerformanceIndexes(sqlite); @@ -1832,6 +1872,94 @@ async function initializeDatabase(): Promise { * synchronously, which better-sqlite3 allows and no remote driver does, so the * table is loaded once here before anything asks for it. */ +function resultRows(result: unknown): T[] { + // node-postgres returns { rows }, mysql2 returns [rows, fields]. + if (Array.isArray(result)) return result[0] as T[]; + return ((result as { rows?: unknown[] }).rows ?? []) as T[]; +} + +async function moveRemoteLegacySudoPasswords(remote: { + execute: (query: SQL) => Promise; +}): Promise { + let rows: LegacySudoRow[]; + try { + rows = resultRows( + await remote.execute( + sql`SELECT id, terminal_config, sudo_password FROM ssh_data WHERE terminal_config LIKE '%sudoPassword%'`, + ), + ); + } catch { + // A database created after the column left never had it. + return; + } + for (const move of legacySudoMoves(rows)) { + await remote.execute( + sql`UPDATE ssh_data SET sudo_password = ${move.sudoPassword} WHERE id = ${move.id}`, + ); + } +} + +/** + * The columns 2.9.0 left in place without a drizzle migration, so drizzle no + * longer knows them. Dropped by hand, only where they still exist. + */ +async function dropRemoteRetired( + dialect: Exclude, + remote: { execute: (query: SQL) => Promise }, +): Promise { + const tables = Object.keys(REMOTE_RETIRED_COLUMNS); + const schema = + dialect === "postgres" ? sql`current_schema()` : sql`DATABASE()`; + let present: Array<{ table_name: string; column_name: string }> = []; + try { + present = resultRows( + await remote.execute( + sql`SELECT table_name AS table_name, column_name AS column_name FROM information_schema.columns WHERE table_schema = ${schema} AND table_name IN (${sql.join( + tables.map((table) => sql`${table}`), + sql`, `, + )})`, + ), + ); + } catch (error) { + databaseLogger.warn("Could not list retired columns", { + operation: "schema_migration", + error: getErrorMessage(error), + }); + return; + } + const existing = new Set( + present.map((row) => `${row.table_name}.${row.column_name}`), + ); + for (const [table, columns] of Object.entries(REMOTE_RETIRED_COLUMNS)) { + for (const column of columns) { + if (!existing.has(`${table}.${column}`)) continue; + try { + await remote.execute( + sql`ALTER TABLE ${sql.identifier(table)} DROP COLUMN ${sql.identifier(column)}`, + ); + } catch (error) { + databaseLogger.warn(`Failed to drop ${table}.${column}`, { + operation: "schema_migration", + error: getErrorMessage(error), + }); + } + } + } + try { + await remote.execute( + sql`DELETE FROM settings WHERE ${sql.identifier("key")} IN (${sql.join( + RETIRED_SETTINGS.map((key) => sql`${key}`), + sql`, `, + )})`, + ); + } catch (error) { + databaseLogger.warn("Failed to delete retired settings", { + operation: "schema_migration", + error: getErrorMessage(error), + }); + } +} + async function initializeRemoteDatabase( dialect: Exclude, ): Promise { @@ -1841,7 +1969,26 @@ async function initializeRemoteDatabase( }); db = await connectRemoteDatabase(dialect); + const remote = db as unknown as { execute: (query: SQL) => Promise }; + await assertNotPre29Database(async (name) => { + try { + await remote.execute(sql`SELECT 1 FROM ${sql.identifier(name)} WHERE 1 = 0`); + return true; + } catch { + return false; + } + }); + await moveRemoteLegacySudoPasswords(remote); + try { + await remote.execute(sql`SELECT oidc_identifier FROM users WHERE 1 = 0`); + const { runSsoLegacyIdentityMigration } = + await import("../../upgrade/sso-legacy-identity-migration.js"); + await runSsoLegacyIdentityMigration(); + } catch { + // The column is gone: the move ran before it was dropped. + } await runRemoteMigrations(dialect, db); + await dropRemoteRetired(dialect, remote); // Imported here rather than at the top: factory.ts imports getDb from this // module, and a static import would close the cycle at module-load time. diff --git a/src/backend/database/db/retired-columns.ts b/src/backend/database/db/retired-columns.ts new file mode 100644 index 000000000..473f0a5a8 --- /dev/null +++ b/src/backend/database/db/retired-columns.ts @@ -0,0 +1,177 @@ +/** + * What 26.10.0 removes that 2.9.0 kept so a downgrade to 2.8 lost nothing. + * Every 2.9 boot copied it into plugin settings, and the upgrade guard only + * opens a database 2.9 has seen. + * + * Kept on purpose: columns a move only reads once the owner's data key opens + * at a password login (users.totp_*, the rdp, vnc and telnet logins and + * domain), so a user who never signed in to 2.9 still gets them, and + * ssh_data.vault_profile_id, whose foreign key SQLite cannot drop in place. + */ + +/** Columns drizzle never knew were removed, dropped by hand on every engine. */ +export const REMOTE_RETIRED_COLUMNS: Record = { + ssh_data: [ + "allow_session_sharing", + "autostart_key", + "autostart_key_password", + "autostart_password", + "default_path", + "docker_config", + "enable_ai_assistant", + "enable_command_history", + "enable_docker", + "enable_file_manager", + "enable_proxmox", + "enable_proxmox_stats", + "enable_rdp", + "enable_session_logging", + "enable_telnet", + "enable_terminal", + "enable_terminal_toolbar", + "enable_tmux_monitor", + "enable_tunnel", + "enable_vnc", + "enable_web_ui", + "guacamole_config", + "ignore_cert", + "mac_address", + "proxmox_config", + "proxmox_stats_config", + "rdp_ignore_cert", + "rdp_port", + "rdp_security", + "scp_legacy", + "security", + "show_docker_in_sidebar", + "show_file_manager_in_sidebar", + "show_server_stats_in_sidebar", + "show_terminal_in_sidebar", + "show_tunnel_in_sidebar", + "stats_config", + "telnet_port", + "tunnel_connections", + "use_warpgate", + "vnc_port", + "web_ui_config", + "wol_broadcast_address", + ], + user_preferences: [ + "ai_assistant_enabled", + "ai_read_only_commands", + "rdp_defaults", + ], +}; + +/** Everything SQLite drops; the first part the drizzle migrations drop too. */ +export const RETIRED_COLUMNS: Record = { + users: [ + "is_oidc", + "oidc_identifier", + "sso_provider_id", + "client_id", + "client_secret", + "issuer_url", + "authorization_url", + "token_url", + "identifier_path", + "name_path", + "scopes", + ], + sessions: ["oidc_sub", "oidc_sid", "sso_provider_id"], + user_preferences: [ + "command_autocomplete", + "terminal_defaults", + "custom_themes", + "confirm_snippet_execution", + "terminal_macros", + ...REMOTE_RETIRED_COLUMNS.user_preferences, + ], + ssh_data: [ + "terminal_config", + "quick_actions", + ...REMOTE_RETIRED_COLUMNS.ssh_data, + ], +}; + +/** Settings rows the plugins copied, and the markers of the copies. */ +export const RETIRED_SETTINGS = [ + "host_defaults", + "analytics_enabled", + "analytics_instance_id", + "terminal_session_timeout_minutes", + "terminal_session_persistence_enabled", + "command_history_enabled", + "touch_input_settings", + "terminal_image_storage_mode", + "terminal_image_local_dir", + "terminal_image_host_path", + "terminal_image_ttl_ms", + "terminal_image_max_count", + "terminal_image_max_storage_bytes", + "session_sharing_globally_enabled", + "guac_enabled", + "guac_url", + "step_ca_url", + "step_ca_fingerprint", + "step_ca_provisioner", + "step_ca_private_endpoint_allowlist", + "acme_ssl_settings", + "global_metrics_interval", + "metrics_history_retention_days", + "tailscale_api_base_url", + "tailscale_api_key", + "ai_globally_enabled", + "ai_private_endpoint_allowlist", + "oidc_silent_login_default", + "host_status_config_migrated", +]; + +/** + * 2.8 editors kept a host's sudo password inside terminal_config, and 2.9 + * still read it from there. Before the column goes it is copied into + * sudo_password, as plaintext, which the field encryption reads as it is + * and encrypts on the next save. + */ +export function legacySudoPassword(terminalConfig: unknown): string | null { + let parsed = terminalConfig; + try { + while (typeof parsed === "string") parsed = JSON.parse(parsed); + } catch { + return null; + } + if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { + return null; + } + const value = (parsed as { sudoPassword?: unknown }).sudoPassword; + return typeof value === "string" && value ? value : null; +} + +export interface LegacySudoRow { + id: number; + terminal_config: unknown; + sudo_password: unknown; +} + +/** The hosts whose sudo password still only lives in terminal_config. */ +export function legacySudoMoves( + rows: LegacySudoRow[], +): Array<{ id: number; sudoPassword: string }> { + return rows.flatMap((row) => { + if (row.sudo_password) return []; + const sudoPassword = legacySudoPassword(row.terminal_config); + return sudoPassword ? [{ id: Number(row.id), sudoPassword }] : []; + }); +} + +/** + * Rewrites the 2.8 `$oidc.preferred_username` placeholder to + * `$external.username` wherever a host username is saved. Plain SQL that + * runs the same on SQLite, Postgres and MySQL; the drizzle migrations carry + * a copy. + */ +export const LEGACY_USERNAME_PLACEHOLDER_UPDATES = [ + "UPDATE ssh_data SET username = REPLACE(username, '$oidc.preferred_username', '$external.username') WHERE username LIKE '%$oidc.preferred_username%'", + "UPDATE ssh_credentials SET username = REPLACE(username, '$oidc.preferred_username', '$external.username') WHERE username LIKE '%$oidc.preferred_username%'", + "UPDATE host_defaults SET value = REPLACE(value, '$oidc.preferred_username', '$external.username') WHERE value LIKE '%$oidc.preferred_username%'", +]; diff --git a/src/backend/database/db/schema.mysql.ts b/src/backend/database/db/schema.mysql.ts index 2c84c750a..38d6ddcde 100644 --- a/src/backend/database/db/schema.mysql.ts +++ b/src/backend/database/db/schema.mysql.ts @@ -26,19 +26,6 @@ export const users = mysqlTable("users", { username: text("username").notNull(), passwordHash: text("password_hash").notNull(), isAdmin: boolean("is_admin").notNull().default(false), - - isOidc: boolean("is_oidc").notNull().default(false), - oidcIdentifier: text("oidc_identifier"), - ssoProviderId: int("sso_provider_id"), - clientId: text("client_id"), - clientSecret: text("client_secret"), - issuerUrl: text("issuer_url"), - authorizationUrl: text("authorization_url"), - tokenUrl: text("token_url"), - identifierPath: text("identifier_path"), - namePath: text("name_path"), - scopes: text().default("openid email profile"), - registeredAt: text("registered_at").notNull().default(sql`(CURRENT_TIMESTAMP)`), donationModalDismissed: boolean("donation_modal_dismissed") .notNull() @@ -60,9 +47,8 @@ export const sessions = mysqlTable( jwtToken: text("jwt_token").notNull(), deviceType: text("device_type").notNull(), deviceInfo: text("device_info").notNull(), - oidcSub: text("oidc_sub"), - oidcSid: text("oidc_sid"), - ssoProviderId: int("sso_provider_id"), + // JSON of the identity provider's logout claims, for back-channel logout. + externalSessionRef: text("external_session_ref"), createdAt: text("created_at") .notNull() .default(sql`(CURRENT_TIMESTAMP)`), @@ -196,11 +182,9 @@ export const hosts = mysqlTable( .notNull() .default(true), statusCheckInterval: int("status_check_interval"), - terminalConfig: text("terminal_config"), // SSH connection options core's connect pipeline reads (keepalive, // legacy algorithms, agent, environment). JSON. sshOptions: text("ssh_options"), - quickActions: text("quick_actions"), notes: text("notes"), enableSsh: boolean("enable_ssh").notNull().default(true), @@ -377,9 +361,6 @@ export const sshFolders = mysqlTable( name: text("name").notNull(), color: text("color"), icon: text("icon"), - credentialId: int("credential_id").references(() => sshCredentials.id, { - onDelete: "set null", - }), // Manual drag-to-reorder position among sibling folders. Null falls back // to name sort, same convention as hosts.sortOrder. sortOrder: int("sort_order"), @@ -712,7 +693,6 @@ export const userPreferences = mysqlTable("user_preferences", { accentColor: text("accent_color"), language: text("language"), storageMode: text("storage_mode"), - commandAutocomplete: boolean("command_autocomplete"), commandPaletteEnabled: boolean("command_palette_enabled"), showHostTags: boolean("show_host_tags"), hostTrayOnClick: boolean("host_tray_on_click"), @@ -720,16 +700,12 @@ export const userPreferences = mysqlTable("user_preferences", { expandAppRailOnHover: boolean("expand_app_rail_on_hover"), showPinAppRailButton: boolean("show_pin_app_rail_button"), foldersCollapsed: boolean("folders_collapsed"), - confirmSnippetExecution: boolean("confirm_snippet_execution"), disableUpdateCheck: boolean("disable_update_check"), confirmTabClose: boolean("confirm_tab_close"), hiddenRailTabs: text("hidden_rail_tabs"), compactHostView: boolean("compact_host_view"), statusColorScheme: text("status_color_scheme"), - customThemes: text("custom_themes"), customKeybindings: text("custom_keybindings"), - terminalDefaults: text("terminal_defaults"), - terminalMacros: text("terminal_macros"), updatedAt: text("updated_at") .notNull() .default(sql`(CURRENT_TIMESTAMP)`), diff --git a/src/backend/database/db/schema.pg.ts b/src/backend/database/db/schema.pg.ts index 3cf5c60a7..4b5afa23b 100644 --- a/src/backend/database/db/schema.pg.ts +++ b/src/backend/database/db/schema.pg.ts @@ -26,19 +26,6 @@ export const users = pgTable("users", { username: text("username").notNull(), passwordHash: text("password_hash").notNull(), isAdmin: boolean("is_admin").notNull().default(false), - - isOidc: boolean("is_oidc").notNull().default(false), - oidcIdentifier: text("oidc_identifier"), - ssoProviderId: integer("sso_provider_id"), - clientId: text("client_id"), - clientSecret: text("client_secret"), - issuerUrl: text("issuer_url"), - authorizationUrl: text("authorization_url"), - tokenUrl: text("token_url"), - identifierPath: text("identifier_path"), - namePath: text("name_path"), - scopes: text().default("openid email profile"), - registeredAt: text("registered_at").notNull().default(sql`CURRENT_TIMESTAMP`), donationModalDismissed: boolean("donation_modal_dismissed") .notNull() @@ -60,9 +47,8 @@ export const sessions = pgTable( jwtToken: text("jwt_token").notNull(), deviceType: text("device_type").notNull(), deviceInfo: text("device_info").notNull(), - oidcSub: text("oidc_sub"), - oidcSid: text("oidc_sid"), - ssoProviderId: integer("sso_provider_id"), + // JSON of the identity provider's logout claims, for back-channel logout. + externalSessionRef: text("external_session_ref"), createdAt: text("created_at") .notNull() .default(sql`CURRENT_TIMESTAMP`), @@ -196,11 +182,9 @@ export const hosts = pgTable( .notNull() .default(true), statusCheckInterval: integer("status_check_interval"), - terminalConfig: text("terminal_config"), // SSH connection options core's connect pipeline reads (keepalive, // legacy algorithms, agent, environment). JSON. sshOptions: text("ssh_options"), - quickActions: text("quick_actions"), notes: text("notes"), enableSsh: boolean("enable_ssh").notNull().default(true), @@ -377,9 +361,6 @@ export const sshFolders = pgTable( name: text("name").notNull(), color: text("color"), icon: text("icon"), - credentialId: integer("credential_id").references(() => sshCredentials.id, { - onDelete: "set null", - }), // Manual drag-to-reorder position among sibling folders. Null falls back // to name sort, same convention as hosts.sortOrder. sortOrder: integer("sort_order"), @@ -712,7 +693,6 @@ export const userPreferences = pgTable("user_preferences", { accentColor: text("accent_color"), language: text("language"), storageMode: text("storage_mode"), - commandAutocomplete: boolean("command_autocomplete"), commandPaletteEnabled: boolean("command_palette_enabled"), showHostTags: boolean("show_host_tags"), hostTrayOnClick: boolean("host_tray_on_click"), @@ -720,16 +700,12 @@ export const userPreferences = pgTable("user_preferences", { expandAppRailOnHover: boolean("expand_app_rail_on_hover"), showPinAppRailButton: boolean("show_pin_app_rail_button"), foldersCollapsed: boolean("folders_collapsed"), - confirmSnippetExecution: boolean("confirm_snippet_execution"), disableUpdateCheck: boolean("disable_update_check"), confirmTabClose: boolean("confirm_tab_close"), hiddenRailTabs: text("hidden_rail_tabs"), compactHostView: boolean("compact_host_view"), statusColorScheme: text("status_color_scheme"), - customThemes: text("custom_themes"), customKeybindings: text("custom_keybindings"), - terminalDefaults: text("terminal_defaults"), - terminalMacros: text("terminal_macros"), updatedAt: text("updated_at") .notNull() .default(sql`CURRENT_TIMESTAMP`), diff --git a/src/backend/database/db/schema.ts b/src/backend/database/db/schema.ts index 3b1732524..81369756f 100644 --- a/src/backend/database/db/schema.ts +++ b/src/backend/database/db/schema.ts @@ -14,19 +14,6 @@ export const users = sqliteTable("users", { username: text("username").notNull(), passwordHash: text("password_hash").notNull(), isAdmin: integer("is_admin", { mode: "boolean" }).notNull().default(false), - - isOidc: integer("is_oidc", { mode: "boolean" }).notNull().default(false), - oidcIdentifier: text("oidc_identifier"), - ssoProviderId: integer("sso_provider_id"), - clientId: text("client_id"), - clientSecret: text("client_secret"), - issuerUrl: text("issuer_url"), - authorizationUrl: text("authorization_url"), - tokenUrl: text("token_url"), - identifierPath: text("identifier_path"), - namePath: text("name_path"), - scopes: text().default("openid email profile"), - registeredAt: text("registered_at").notNull().default(sql`CURRENT_TIMESTAMP`), donationModalDismissed: integer("donation_modal_dismissed", { mode: "boolean", @@ -50,9 +37,8 @@ export const sessions = sqliteTable( jwtToken: text("jwt_token").notNull(), deviceType: text("device_type").notNull(), deviceInfo: text("device_info").notNull(), - oidcSub: text("oidc_sub"), - oidcSid: text("oidc_sid"), - ssoProviderId: integer("sso_provider_id"), + // JSON of the identity provider's logout claims, for back-channel logout. + externalSessionRef: text("external_session_ref"), createdAt: text("created_at") .notNull() .default(sql`CURRENT_TIMESTAMP`), @@ -188,11 +174,9 @@ export const hosts = sqliteTable( .notNull() .default(true), statusCheckInterval: integer("status_check_interval"), - terminalConfig: text("terminal_config"), // SSH connection options core's connect pipeline reads (keepalive, // legacy algorithms, agent, environment). JSON. sshOptions: text("ssh_options"), - quickActions: text("quick_actions"), notes: text("notes"), enableSsh: integer("enable_ssh", { mode: "boolean" }).notNull().default(true), @@ -369,9 +353,6 @@ export const sshFolders = sqliteTable( name: text("name").notNull(), color: text("color"), icon: text("icon"), - credentialId: integer("credential_id").references(() => sshCredentials.id, { - onDelete: "set null", - }), // Manual drag-to-reorder position among sibling folders. Null falls back // to name sort, same convention as hosts.sortOrder. sortOrder: integer("sort_order"), @@ -704,7 +685,6 @@ export const userPreferences = sqliteTable("user_preferences", { accentColor: text("accent_color"), language: text("language"), storageMode: text("storage_mode"), - commandAutocomplete: integer("command_autocomplete", { mode: "boolean" }), commandPaletteEnabled: integer("command_palette_enabled", { mode: "boolean" }), showHostTags: integer("show_host_tags", { mode: "boolean" }), hostTrayOnClick: integer("host_tray_on_click", { mode: "boolean" }), @@ -716,16 +696,12 @@ export const userPreferences = sqliteTable("user_preferences", { mode: "boolean", }), foldersCollapsed: integer("folders_collapsed", { mode: "boolean" }), - confirmSnippetExecution: integer("confirm_snippet_execution", { mode: "boolean" }), disableUpdateCheck: integer("disable_update_check", { mode: "boolean" }), confirmTabClose: integer("confirm_tab_close", { mode: "boolean" }), hiddenRailTabs: text("hidden_rail_tabs"), compactHostView: integer("compact_host_view", { mode: "boolean" }), statusColorScheme: text("status_color_scheme"), - customThemes: text("custom_themes"), customKeybindings: text("custom_keybindings"), - terminalDefaults: text("terminal_defaults"), - terminalMacros: text("terminal_macros"), updatedAt: text("updated_at") .notNull() .default(sql`CURRENT_TIMESTAMP`), diff --git a/src/backend/database/db/upgrade-guard.ts b/src/backend/database/db/upgrade-guard.ts new file mode 100644 index 000000000..a445203c4 --- /dev/null +++ b/src/backend/database/db/upgrade-guard.ts @@ -0,0 +1,17 @@ +/** + * 26.10.0 dropped the columns 2.9.0 kept for a downgrade to 2.8, and the + * one-time 2.8 moves that copied them into plugins went with them. A 2.8 + * database has to pass through 2.9 first, or that data is lost. + * + * A 2.9 database has the plugins table; a 2.8 one has hosts but no plugins. + */ +export const PRE_29_DATABASE_ERROR = + "This database is from Termix 2.8 or older. Upgrade to Termix 2.9.1 and start it once so it can move your data, then upgrade to this version."; + +export async function assertNotPre29Database( + hasTable: (name: string) => boolean | Promise, +): Promise { + if ((await hasTable("ssh_data")) && !(await hasTable("plugins"))) { + throw new Error(PRE_29_DATABASE_ERROR); + } +} diff --git a/src/backend/database/repositories/host-folder-repository.ts b/src/backend/database/repositories/host-folder-repository.ts index 1d88bcb06..2330084fe 100644 --- a/src/backend/database/repositories/host-folder-repository.ts +++ b/src/backend/database/repositories/host-folder-repository.ts @@ -112,7 +112,6 @@ export class HostFolderRepository { name: string, color: string | null | undefined, icon: string | null | undefined, - credentialId?: number | null, now = new Date().toISOString(), ): Promise<{ folder: HostFolderRecord; created: boolean }> { const existing = await this.findFolder(userId, name); @@ -123,8 +122,6 @@ export class HostFolderRepository { { color, icon, - credentialId: - credentialId === undefined ? existing.credentialId : credentialId, updatedAt: now, }, and(eq(sshFolders.userId, userId), eq(sshFolders.name, name)), @@ -140,7 +137,6 @@ export class HostFolderRepository { name, color, icon, - credentialId: credentialId ?? null, createdAt: now, updatedAt: now, }); diff --git a/src/backend/database/repositories/host-resolution-repository.ts b/src/backend/database/repositories/host-resolution-repository.ts index 181e85382..580850d06 100644 --- a/src/backend/database/repositories/host-resolution-repository.ts +++ b/src/backend/database/repositories/host-resolution-repository.ts @@ -1,5 +1,5 @@ import { and, eq, inArray, sql } from "drizzle-orm"; -import { hosts, sshCredentials, sshFolders } from "../db/schema.js"; +import { hosts, sshCredentials } from "../db/schema.js"; import type { DatabaseContext } from "./database-context.js"; import { DataCrypto } from "../../utils/data-crypto.js"; @@ -459,34 +459,6 @@ export class HostResolutionRepository { return this.decryptOne("ssh_credentials", rows[0], decryptUserId); } - /** - * Resolve the nearest assigned credential for a folder path, walking up - * through parent folders (e.g. "Switches / Floor1" falls back to - * "Switches" if the child folder has no credential of its own). - */ - async findFolderCredentialId( - userId: string, - folderPath: string, - ): Promise { - const segments = folderPath.split(" / ").filter(Boolean); - if (segments.length === 0) return null; - - const paths = segments.map((_, i) => segments.slice(0, i + 1).join(" / ")); - const rows = await this.context.drizzle - .select({ name: sshFolders.name, credentialId: sshFolders.credentialId }) - .from(sshFolders) - .where( - and(eq(sshFolders.userId, userId), inArray(sshFolders.name, paths)), - ); - - const byName = new Map(rows.map((row) => [row.name, row.credentialId])); - for (let i = paths.length - 1; i >= 0; i--) { - const credentialId = byName.get(paths[i]); - if (credentialId) return credentialId; - } - return null; - } - private decryptOne>( tableName: "ssh_data" | "ssh_credentials", record: T | undefined, diff --git a/src/backend/database/repositories/user-repository.ts b/src/backend/database/repositories/user-repository.ts index 0bc7b0de0..a04942637 100644 --- a/src/backend/database/repositories/user-repository.ts +++ b/src/backend/database/repositories/user-repository.ts @@ -1,4 +1,4 @@ -import { and, asc, eq, inArray, like, sql } from "drizzle-orm"; +import { and, asc, eq, getTableColumns, inArray, like, sql } from "drizzle-orm"; import { pluginSettings, users } from "../db/schema.js"; import type { DatabaseContext } from "./database-context.js"; import { @@ -8,11 +8,22 @@ import { } from "./mutation-result.js"; import { insertReturning, updateReturning } from "./returning.js"; -export type UserRecord = typeof users.$inferSelect; +/** isExternal: the user has at least one external sign-in identity. */ +export type UserRecord = typeof users.$inferSelect & { isExternal: boolean }; export type NewUserRecord = typeof users.$inferInsert; export type UserUpdate = Partial>; export type NewFirstLocalUserRecord = Omit; +// Qualified by hand: drizzle prints a column in a subquery without its table, +// which would bind users.id to the identity row's own id. +const userColumns = { + ...getTableColumns(users), + isExternal: + sql`EXISTS (SELECT 1 FROM ${sql.identifier("user_external_identities")} WHERE ${sql.identifier("user_external_identities")}.${sql.identifier("user_id")} = ${sql.identifier("users")}.${sql.identifier("id")})`.mapWith( + (value: unknown) => value === true || Number(value) === 1, + ), +}; + export class UserRepository { constructor( private readonly context: DatabaseContext, @@ -20,7 +31,7 @@ export class UserRepository { ) {} async listAll(): Promise { - return this.context.drizzle.select().from(users); + return this.context.drizzle.select(userColumns).from(users); } /** @@ -42,7 +53,7 @@ export class UserRepository { const [rows, totalResult] = await Promise.all([ this.context.drizzle - .select() + .select(userColumns) .from(users) .where(where) // Case-insensitive: a plain sort puts every capitalised name ahead of @@ -61,7 +72,7 @@ export class UserRepository { async findById(id: string): Promise { const rows = await this.context.drizzle - .select() + .select(userColumns) .from(users) .where(eq(users.id, id)) .limit(1); @@ -71,7 +82,7 @@ export class UserRepository { async findByUsername(username: string): Promise { const rows = await this.context.drizzle - .select() + .select(userColumns) .from(users) .where(eq(users.username, username)) .limit(1); @@ -79,18 +90,6 @@ export class UserRepository { return rows[0] ?? null; } - async findByExternalIdentifier( - oidcIdentifier: string, - ): Promise { - const rows = await this.context.drizzle - .select() - .from(users) - .where(eq(users.oidcIdentifier, oidcIdentifier)) - .limit(1); - - return rows[0] ?? null; - } - async listByIds(ids: string[]): Promise { const uniqueIds = Array.from(new Set(ids.filter(Boolean))); if (uniqueIds.length === 0) { @@ -98,7 +97,7 @@ export class UserRepository { } return this.context.drizzle - .select() + .select(userColumns) .from(users) .where(inArray(users.id, uniqueIds)); } @@ -106,7 +105,9 @@ export class UserRepository { async create(user: NewUserRecord): Promise { const rows = await insertReturning(this.context, users, user); await this.afterWrite(); - return rows[0]; + return ( + (await this.findById(rows[0].id)) ?? { ...rows[0], isExternal: false } + ); } async createFirstLocalUser( @@ -118,7 +119,7 @@ export class UserRepository { })); await this.afterWrite(); - return result; + return { ...result, user: { ...result.user, isExternal: false } }; } async createFirstSsoUser( @@ -130,7 +131,7 @@ export class UserRepository { })); await this.afterWrite(); - return result; + return { ...result, user: { ...result.user, isExternal: true } }; } /** @@ -146,7 +147,7 @@ export class UserRepository { */ private async createCheckingIfFirst( build: (isFirstUser: boolean) => NewUserRecord, - ): Promise<{ user: UserRecord; isFirstUser: boolean }> { + ): Promise<{ user: typeof users.$inferSelect; isFirstUser: boolean }> { if (this.context.dialect === "sqlite") { /* eslint-disable no-restricted-syntax -- sqlite-only branch: the dialect is checked directly above, and better-sqlite3 rejects an async @@ -195,7 +196,7 @@ export class UserRepository { ); await this.afterWrite(); - return rows[0] ?? null; + return rows[0] ? this.findById(rows[0].id) : null; } async delete(id: string): Promise { diff --git a/src/backend/database/routes/host-bulk-routes.ts b/src/backend/database/routes/host-bulk-routes.ts index 33c203b23..9e247d2a8 100644 --- a/src/backend/database/routes/host-bulk-routes.ts +++ b/src/backend/database/routes/host-bulk-routes.ts @@ -710,9 +710,6 @@ export function registerHostBulkRoutes( sudoPassword: hostData.sudoPassword || null, jumpHosts: jumpHosts ? JSON.stringify(jumpHosts) : null, ...importedStatusCheck(hostData as Record), - terminalConfig: hostData.terminalConfig - ? JSON.stringify(hostData.terminalConfig) - : null, // A 2.8 export carries these inside terminalConfig. sshOptions: sshOptionsForWrite({ @@ -975,7 +972,6 @@ export function registerHostBulkRoutes( : null, statusCheckEnabled: true, statusCheckInterval: null, - terminalConfig: null, sshOptions: null, forceKeyboardInteractive: "false", notes: null, diff --git a/src/backend/database/routes/host-folder-routes.ts b/src/backend/database/routes/host-folder-routes.ts index dfddd50e0..4ef829549 100644 --- a/src/backend/database/routes/host-folder-routes.ts +++ b/src/backend/database/routes/host-folder-routes.ts @@ -12,7 +12,10 @@ import { } from "../repositories/factory.js"; import { isNonEmptyString } from "./host-normalizers.js"; import { recompute } from "../../hosts/defaults/recompute.js"; -import { setFolderCredentialDefault } from "../../hosts/defaults/service.js"; +import { + folderCredentialIds, + setFolderCredentialDefault, +} from "../../hosts/defaults/service.js"; type HostFolderRoutesDeps = { authenticateJWT: RequestHandler; @@ -143,10 +146,17 @@ export function registerHostFolderRoutes( } try { - const folders = - await createCurrentHostFolderRepository().listFolders(userId); + const [folders, credentials] = await Promise.all([ + createCurrentHostFolderRepository().listFolders(userId), + folderCredentialIds(userId), + ]); - res.json(folders); + res.json( + folders.map((folder) => ({ + ...folder, + credentialId: credentials.get(folder.id) ?? null, + })), + ); } catch (err) { sshLogger.error("Failed to fetch folders", err, { operation: "fetch_folders", @@ -238,7 +248,6 @@ export function registerHostFolderRoutes( name, color, icon, - normalizedCredentialId, ); if (typeof localOnly === "boolean") { await createCurrentHostFolderRepository().setLocalOnly( diff --git a/src/backend/database/routes/host-normalizers.ts b/src/backend/database/routes/host-normalizers.ts index 15457f15e..f02a1ba1b 100644 --- a/src/backend/database/routes/host-normalizers.ts +++ b/src/backend/database/routes/host-normalizers.ts @@ -62,6 +62,7 @@ export const OWNER_PRIVATE_AUTH_FIELDS = { ], } as const; +/** Keys a 2.8 client's terminalConfig may carry that only the owner may set. */ export const OWNER_PRIVATE_TERMINAL_CONFIG_FIELDS = [ "sudoPassword", "agentSocketPath", @@ -263,18 +264,10 @@ export function stripSensitiveFields( host: Record, ): Record { const result = { ...host }; - const terminalConfigForSudo = - host.terminalConfig && - typeof host.terminalConfig === "object" && - !Array.isArray(host.terminalConfig) - ? (host.terminalConfig as Record) - : undefined; result.hasKey = !!host.key; result.hasKeyPassword = !!host.keyPassword; result.hasPassword = !!host.password; - // 2.8 editors kept the sudo password inside terminal_config. - result.hasSudoPassword = - !!host.sudoPassword || !!terminalConfigForSudo?.sudoPassword; + result.hasSudoPassword = !!host.sudoPassword; for (const field of SENSITIVE_FIELDS) { delete result[field]; } @@ -283,11 +276,11 @@ export function stripSensitiveFields( typeof result.terminalConfig === "object" && !Array.isArray(result.terminalConfig) ) { - const terminalConfig = { - ...(result.terminalConfig as Record), - }; - delete terminalConfig.sudoPassword; - result.terminalConfig = terminalConfig; + const { sudoPassword: _sudo, ...rest } = result.terminalConfig as Record< + string, + unknown + >; + result.terminalConfig = rest; } return result; } @@ -397,44 +390,22 @@ export function parseSharedSource( } } -function parseJsonObject(value: unknown): Record | null { - if (value && typeof value === "object" && !Array.isArray(value)) { - return value as Record; - } - if (typeof value !== "string" || !value) return null; - try { - const parsed = JSON.parse(value); - return parsed && typeof parsed === "object" && !Array.isArray(parsed) - ? parsed - : null; - } catch { - return null; - } -} - /** * sshOptions, and terminalConfig in the shape 2.8 clients and exports read: * core's own keys only, the SSH options. Everything else 2.8 kept there now * belongs to plugins' host settings, which they put back through their - * hostPayloadLegacy. A sudo password 2.8 kept in terminal_config comes out as - * sudoPassword, for the sanitizers to handle. + * hostPayloadLegacy. */ export function hostTerminalExport(host: Record): { sshOptions: HostSshOptions; terminalConfig?: Record; - sudoPassword?: unknown; } { - const raw = parseJsonObject(host.terminalConfig); - const sshOptions = parseSshOptions( - host.sshOptions != null ? host.sshOptions : raw, - ); + const sshOptions = parseSshOptions(host.sshOptions); const terminalConfig: Record = { ...sshOptions }; - const legacySudo = !host.sudoPassword ? raw?.sudoPassword : undefined; return { sshOptions, terminalConfig: Object.keys(terminalConfig).length > 0 ? terminalConfig : undefined, - ...(legacySudo ? { sudoPassword: legacySudo } : {}), }; } diff --git a/src/backend/database/routes/host-terminal-fields.ts b/src/backend/database/routes/host-terminal-fields.ts index e65330ca4..8b7c5c69c 100644 --- a/src/backend/database/routes/host-terminal-fields.ts +++ b/src/backend/database/routes/host-terminal-fields.ts @@ -1,14 +1,11 @@ /** - * How a host update writes terminal_config and ssh_options, kept apart from - * the route so it can be tested on its own. + * How a host update writes ssh_options, kept apart from the route so it can + * be tested on its own. */ import { createCurrentHostResolutionRepository } from "../repositories/factory.js"; import { parseSshOptions } from "../../hosts/ssh-options.js"; -import { - OWNER_PRIVATE_SSH_OPTION_FIELDS, - OWNER_PRIVATE_TERMINAL_CONFIG_FIELDS, -} from "./host-normalizers.js"; +import { OWNER_PRIVATE_SSH_OPTION_FIELDS } from "./host-normalizers.js"; export function parseTerminalConfig( value: unknown, @@ -27,63 +24,41 @@ export function parseTerminalConfig( } /** - * terminal_config is merged into what is stored rather than replaced. The - * 2.8 keys the editor no longer sends (the terminal's look, which the - * ssh-terminal plugin copies into its host settings, and a sudo password 2.8 - * editors kept there) stay until 26.10.0 drops the column. A shared editor - * never changes the owner's private keys in either JSON. Returns an error - * for a terminalConfig that cannot be read. + * A 2.8 client's terminalConfig only feeds sshOptions, which the route has + * already taken from it. A shared editor never changes the owner's private + * SSH options. Returns an error for a terminalConfig that cannot be read. */ -export async function mergeStoredTerminalFields( +export async function mergeStoredSshOptions( sshDataObj: Record, hostData: Record, hostId: number, ownerId: string, isOwner: boolean, ): Promise { - const sentTerminalConfig = - hostData.terminalConfig !== undefined && hostData.terminalConfig !== null; - const incoming = sentTerminalConfig - ? parseTerminalConfig(hostData.terminalConfig) - : null; - if (sentTerminalConfig && !incoming) return "Invalid terminal config"; + if ( + hostData.terminalConfig !== undefined && + hostData.terminalConfig !== null && + !parseTerminalConfig(hostData.terminalConfig) + ) { + return "Invalid terminal config"; + } delete sshDataObj.terminalConfig; - const sentSudoPassword = isOwner && hostData.sudoPassword !== undefined; - if (!incoming && !sentSudoPassword && sshDataObj.sshOptions === undefined) { - return null; - } + if (isOwner || sshDataObj.sshOptions === undefined) return null; const stored = await createCurrentHostResolutionRepository().findHostById( hostId, ownerId, ); - const storedConfig = parseTerminalConfig(stored?.terminalConfig) ?? {}; - - if (incoming || (sentSudoPassword && "sudoPassword" in storedConfig)) { - const merged = { ...storedConfig, ...(incoming ?? {}) }; - if (!isOwner) { - for (const field of OWNER_PRIVATE_TERMINAL_CONFIG_FIELDS) { - if (field in storedConfig) merged[field] = storedConfig[field]; - else delete merged[field]; - } + const options = parseSshOptions(sshDataObj.sshOptions); + const storedOptions = parseSshOptions(stored?.sshOptions); + for (const field of OWNER_PRIVATE_SSH_OPTION_FIELDS) { + if (storedOptions[field] !== undefined) { + options[field] = storedOptions[field]; + } else { + delete options[field]; } - // A sudo password saved on its own lives in the encrypted column. - if (sentSudoPassword) delete merged.sudoPassword; - sshDataObj.terminalConfig = JSON.stringify(merged); - } - - if (!isOwner && sshDataObj.sshOptions !== undefined) { - const options = parseSshOptions(sshDataObj.sshOptions); - const storedOptions = parseSshOptions(stored?.sshOptions ?? storedConfig); - for (const field of OWNER_PRIVATE_SSH_OPTION_FIELDS) { - if (storedOptions[field] !== undefined) { - options[field] = storedOptions[field]; - } else { - delete options[field]; - } - } - sshDataObj.sshOptions = JSON.stringify(options); } + sshDataObj.sshOptions = JSON.stringify(options); return null; } diff --git a/src/backend/database/routes/host.ts b/src/backend/database/routes/host.ts index 31e75a726..5644140ec 100644 --- a/src/backend/database/routes/host.ts +++ b/src/backend/database/routes/host.ts @@ -98,7 +98,7 @@ import { listHostProtocols, } from "../../hosts/protocol-auth/registry.js"; import { - mergeStoredTerminalFields, + mergeStoredSshOptions, parseTerminalConfig, } from "./host-terminal-fields.js"; @@ -316,11 +316,6 @@ router.post( jumpHosts: Array.isArray(jumpHosts) ? JSON.stringify(jumpHosts) : null, statusCheckEnabled: statusCheckEnabled === false ? 0 : 1, statusCheckInterval: normalizeStatusInterval(statusCheckInterval), - terminalConfig: terminalConfig - ? typeof terminalConfig === "string" - ? terminalConfig - : JSON.stringify(terminalConfig) - : null, sshOptions: sshOptionsForWrite({ sshOptions, terminalConfig }) ?? null, forceKeyboardInteractive: forceKeyboardInteractive ? "true" : "false", notes: notes || null, @@ -889,11 +884,6 @@ router.put( jumpHosts: Array.isArray(jumpHosts) ? JSON.stringify(jumpHosts) : null, statusCheckEnabled: statusCheckEnabled === false ? 0 : 1, statusCheckInterval: normalizeStatusInterval(statusCheckInterval), - terminalConfig: terminalConfig - ? typeof terminalConfig === "string" - ? terminalConfig - : JSON.stringify(terminalConfig) - : null, forceKeyboardInteractive: forceKeyboardInteractive ? "true" : "false", notes: notes || null, useSocks5: useSocks5 ? 1 : 0, @@ -1081,7 +1071,7 @@ router.put( } } - const terminalFieldsError = await mergeStoredTerminalFields( + const terminalFieldsError = await mergeStoredSshOptions( sshDataObj, hostData, Number(hostId), @@ -1661,11 +1651,7 @@ router.get( } const resolved = (await resolveHostCredentials(host, userId)) || host; - let value = resolved[field]; - - if (!value && field === "sudoPassword") { - value = hostTerminalExport(resolved).sudoPassword || null; - } + const value = resolved[field]; if (!value) { return res.status(404).json({ error: "No password set" }); @@ -1775,10 +1761,7 @@ router.get( credentialId: resolvedHost.credentialId || null, overrideCredentialUsername: !!resolvedHost.overrideCredentialUsername, - sudoPassword: - resolvedHost.sudoPassword || - hostTerminalExport(resolvedHost).sudoPassword || - null, + sudoPassword: resolvedHost.sudoPassword || null, jumpHosts: resolvedHost.jumpHosts ? JSON.parse(resolvedHost.jumpHosts as string) : null, @@ -1911,9 +1894,7 @@ router.get( !!resolvedHost.overrideCredentialUsername, sudoPassword: shareMode ? null - : resolvedHost.sudoPassword || - hostTerminalExport(resolvedHost).sudoPassword || - null, + : resolvedHost.sudoPassword || null, jumpHosts: resolvedHost.jumpHosts ? JSON.parse(resolvedHost.jumpHosts as string) : null, diff --git a/src/backend/database/routes/user-admin-routes.ts b/src/backend/database/routes/user-admin-routes.ts index 366d89a6e..cd4381b1c 100644 --- a/src/backend/database/routes/user-admin-routes.ts +++ b/src/backend/database/routes/user-admin-routes.ts @@ -123,7 +123,6 @@ export function registerUserAdminRoutes( username: u.username, is_admin: u.isAdmin, is_external: isExternalAccount(u), - is_oidc: u.isOidc, password_hash: u.passwordHash ? "set" : null, // Management-only details stay admin-eyes-only; regular users hit // this route to pick sharing targets. @@ -470,15 +469,6 @@ export function registerUserAdminRoutes( username, passwordHash: password_hash, isAdmin: false, - isOidc: false, - clientId: "", - clientSecret: "", - issuerUrl: "", - authorizationUrl: "", - tokenUrl: "", - identifierPath: "", - namePath: "", - scopes: "openid email profile", }); try { @@ -631,7 +621,7 @@ export function registerUserAdminRoutes( if (!targetUser) { return res.status(404).json({ error: "User not found" }); } - if (targetUser.isOidc && !targetUser.passwordHash) { + if (targetUser.isExternal && !targetUser.passwordHash) { return res.status(400).json({ error: "This user authenticates through an external provider", }); diff --git a/src/backend/database/routes/user-external-account-routes.ts b/src/backend/database/routes/user-external-account-routes.ts index dd5694ba2..296fd1045 100644 --- a/src/backend/database/routes/user-external-account-routes.ts +++ b/src/backend/database/routes/user-external-account-routes.ts @@ -58,7 +58,7 @@ export function registerUserExternalAccountRoutes( * /users/link-external-to-password: * post: * summary: Merge an external account into a password account - * description: Moves an external-only account's sign-in identities onto a password account and deletes the external-only account (admin only). A 2.8 SSO account whose only sign-in is the matching legacy OIDC link is accepted as the target too, to merge a duplicate made by 2.9.0. The 2.8 path /users/link-oidc-to-password and its oidcUserId field are accepted too. + * description: Moves an external-only account's sign-in identities onto a password account and deletes the external-only account (admin only). A 2.8 SSO account whose only sign-in is the matching legacy OIDC link is accepted as the target too, to merge a duplicate made by 2.9.0. * tags: * - Users * requestBody: @@ -86,7 +86,7 @@ export function registerUserExternalAccountRoutes( */ const link = async (req: Request, res: Response) => { const adminUserId = (req as AuthenticatedRequest).userId; - const externalUserId = req.body?.externalUserId ?? req.body?.oidcUserId; + const externalUserId = req.body?.externalUserId; const targetUsername = req.body?.targetUsername; if ( @@ -143,11 +143,6 @@ export function registerUserExternalAccountRoutes( adminUserId, }); - await userRepository.update(targetUser.id, { - isOidc: true, - oidcIdentifier: - externalUser.oidcIdentifier ?? targetUser.oidcIdentifier, - }); await identities.moveIdentities(externalUserId, targetUser.id); await authManager.revokeAllUserSessions(externalUserId); @@ -201,7 +196,7 @@ export function registerUserExternalAccountRoutes( * /users/unlink-external-from-password: * post: * summary: Remove external sign-in from a password account - * description: Removes every external sign-in identity from an account that also has a password (admin only). The 2.8 path /users/unlink-oidc-from-password is accepted too. + * description: Removes every external sign-in identity from an account that also has a password (admin only). * tags: * - Users * requestBody: @@ -261,10 +256,6 @@ export function registerUserExternalAccountRoutes( }); } - await userRepository.update(targetUser.id, { - isOidc: false, - oidcIdentifier: null, - }); await createCurrentUserAuthRepository().unlinkIdentitiesForUser( targetUser.id, ); @@ -307,7 +298,4 @@ export function registerUserExternalAccountRoutes( router.post("/link-external-to-password", authenticateJWT, link); router.post("/unlink-external-from-password", authenticateJWT, unlink); - // 2.8 paths, kept until 26.10.0. - router.post("/link-oidc-to-password", authenticateJWT, link); - router.post("/unlink-oidc-from-password", authenticateJWT, unlink); } diff --git a/src/backend/database/routes/user-password-reset-routes.ts b/src/backend/database/routes/user-password-reset-routes.ts index ef5b57a87..20a60caaf 100644 --- a/src/backend/database/routes/user-password-reset-routes.ts +++ b/src/backend/database/routes/user-password-reset-routes.ts @@ -173,7 +173,7 @@ export function registerUserPasswordResetRoutes( }); } - if (user.isOidc) { + if (user.isExternal) { return res.json({ message: "If the user exists, a password reset code has been generated. Check docker logs for the code.", diff --git a/src/backend/database/routes/users.ts b/src/backend/database/routes/users.ts index 415fa6820..c1b273798 100644 --- a/src/backend/database/routes/users.ts +++ b/src/backend/database/routes/users.ts @@ -55,6 +55,7 @@ import { isNativeAppRequest, syncSharedCredentialsForUserRoles, } from "../../auth/session-issuer.js"; +import { EXTERNAL_AUTO_PROVISION_KEY } from "../../auth/provisioning.js"; const authManager = AuthManager.getInstance(); @@ -184,15 +185,6 @@ router.post("/create", async (req, res) => { id, username, passwordHash: password_hash, - isOidc: false, - clientId: "", - clientSecret: "", - issuerUrl: "", - authorizationUrl: "", - tokenUrl: "", - identifierPath: "", - namePath: "", - scopes: "openid email profile", }); try { @@ -361,7 +353,7 @@ router.post("/proxy-login", async (req, res) => { return res.status(403).json({ error: "Proxy user must already exist" }); } if ( - userRecord.isOidc || + userRecord.isExternal || (await createCurrentUserAuthRepository().hasSecondFactor(userRecord.id)) ) { return res.status(409).json({ @@ -600,8 +592,7 @@ router.get("/me", authenticateJWT, async (req: Request, res: Response) => { } const hasPassword = user.passwordHash && user.passwordHash.trim() !== ""; - const isDualAuth = - hasPassword && isExternalAccount(user) && !!user.oidcIdentifier; + const isDualAuth = hasPassword && isExternalAccount(user); const showDonationModal = shouldShowDonationModal( user.registeredAt, @@ -613,8 +604,6 @@ router.get("/me", authenticateJWT, async (req: Request, res: Response) => { username: user.username, is_admin: !!user.isAdmin, is_external: isExternalAccount(user), - // 2.8 name, kept until 26.10.0. - is_oidc: !!user.isOidc, is_dual_auth: isDualAuth, // Any second factor; the name is what 2.8 clients read. totp_enabled: await createCurrentUserAuthRepository().hasSecondFactor( @@ -944,7 +933,7 @@ router.patch("/registration-allowed", authenticateJWT, async (req, res) => { * /users/external-auto-provision: * get: * summary: Get the external account auto-create setting - * description: Whether a new account is created the first time someone signs in through an external login (SSO, LDAP or any login plugin). The 2.8 path /users/oidc-auto-provision is accepted too. + * description: Whether a new account is created the first time someone signs in through an external login (SSO, LDAP or any login plugin). * tags: * - Users * responses: @@ -955,7 +944,7 @@ const getExternalAutoProvision: RequestHandler = async (_req, res) => { try { res.json({ enabled: await createCurrentSettingsRepository().getBoolean( - "oidc_auto_provision", + EXTERNAL_AUTO_PROVISION_KEY, false, ), }); @@ -972,7 +961,7 @@ const getExternalAutoProvision: RequestHandler = async (_req, res) => { * /users/external-auto-provision: * patch: * summary: Set the external account auto-create setting - * description: Enables or disables creating an account on first external sign-in. The 2.8 path /users/oidc-auto-provision is accepted too. + * description: Enables or disables creating an account on first external sign-in. * tags: * - Users * requestBody: @@ -1006,7 +995,7 @@ const setExternalAutoProvision: RequestHandler = async (req, res) => { return res.status(400).json({ error: "Invalid value for enabled" }); } await createCurrentSettingsRepository().set( - "oidc_auto_provision", + EXTERNAL_AUTO_PROVISION_KEY, enabled ? "true" : "false", ); res.json({ enabled }); @@ -1022,9 +1011,6 @@ router.patch( authenticateJWT, setExternalAutoProvision, ); -// 2.8 paths, kept until 26.10.0. -router.get("/oidc-auto-provision", getExternalAutoProvision); -router.patch("/oidc-auto-provision", authenticateJWT, setExternalAutoProvision); /** * @openapi @@ -1317,7 +1303,7 @@ router.delete("/delete-account", authenticateJWT, async (req, res) => { return res.status(404).json({ error: "User not found" }); } - if (userRecord.isOidc) { + if (userRecord.isExternal) { return res.status(403).json({ error: "Cannot delete external authentication accounts through this endpoint", diff --git a/src/backend/hosts/credential-username.ts b/src/backend/hosts/credential-username.ts index d03ad704d..fc620ef8d 100644 --- a/src/backend/hosts/credential-username.ts +++ b/src/backend/hosts/credential-username.ts @@ -34,14 +34,13 @@ export function pickResolvedPassword( return undefined; } -const USERNAME_PLACEHOLDER = - /\$(?:external\.username|oidc\.preferred_username)/g; +const USERNAME_PLACEHOLDER = /\$external\.username/g; /** - * Expands `$external.username` (or its 2.8 spelling `$oidc.preferred_username`) - * in an SSH username to the name the connecting user signed in with through - * SSO or LDAP. Returns the username unchanged if it has no placeholder or the - * user has no external sign-in. + * Expands `$external.username` in an SSH username to the name the connecting + * user signed in with through SSO or LDAP: the subject of their first + * external identity. Returns the username unchanged if it has no placeholder + * or the user has no external sign-in. */ export async function expandExternalUsername( username: string | undefined, @@ -52,31 +51,14 @@ export async function expandExternalUsername( } try { - const { createCurrentUserRepository } = + const { createCurrentUserAuthRepository } = await import("../database/repositories/factory.js"); - const user = await createCurrentUserRepository().findById(userId); - let externalName = user?.oidcIdentifier; + const identities = + await createCurrentUserAuthRepository().listIdentitiesForUser(userId); + const externalName = [...identities].sort((a, b) => a.id - b.id)[0] + ?.subject; if (!externalName) return username; - const match = /^ldap:(\d+):(.+)$/.exec(externalName); - if (match) { - // Only strip the prefix for a real LDAP identity, to prevent spoofing - // through an SSO subject that happens to look like one. - const { createCurrentUserAuthRepository } = - await import("../database/repositories/factory.js"); - const identities = - await createCurrentUserAuthRepository().listIdentitiesForUser(userId); - if ( - identities.some( - (identity) => - identity.providerId === `ldap:${match[1]}` && - identity.subject === match[2], - ) - ) { - externalName = match[2]; - } - } - return username.replace(USERNAME_PLACEHOLDER, () => externalName); } catch { return username; diff --git a/src/backend/hosts/defaults/service.ts b/src/backend/hosts/defaults/service.ts index 30a3441bc..8cb070651 100644 --- a/src/backend/hosts/defaults/service.ts +++ b/src/backend/hosts/defaults/service.ts @@ -258,6 +258,35 @@ export async function resolveForEditor(input: { return resolveAll(catalog, levels, input.hostId ?? null); } +/** Each folder's credential, from its auth default, keyed by folder id. */ +export async function folderCredentialIds( + userId: string, +): Promise> { + const result = new Map(); + const rows = await createCurrentHostDefaultsRepository().listOwnedBy(userId); + for (const row of rows) { + if ( + row.level !== "folder" || + row.folderId == null || + row.namespace !== CORE_NAMESPACE || + row.key !== "auth" + ) { + continue; + } + const auth = decodeDefaultValue(row.value) as { + authType?: string; + credentialId?: unknown; + } | null; + if ( + auth?.authType === "credential" && + typeof auth.credentialId === "number" + ) { + result.set(row.folderId, auth.credentialId); + } + } + return result; +} + /** * A folder's credential is its auth default: hosts in the folder that follow * their defaults sign in with it. Null clears it. diff --git a/src/backend/hosts/host-resolver.ts b/src/backend/hosts/host-resolver.ts index 771553b38..27b871956 100644 --- a/src/backend/hosts/host-resolver.ts +++ b/src/backend/hosts/host-resolver.ts @@ -106,37 +106,11 @@ export async function resolveHostById( host.jumpHosts = []; } } - if (typeof host.terminalConfig === "string" && host.terminalConfig) { - try { - host.terminalConfig = JSON.parse(host.terminalConfig as string); - } catch { - host.terminalConfig = undefined; - } - } - if ( - host.terminalConfig && - typeof host.terminalConfig === "object" && - !Array.isArray(host.terminalConfig) - ) { - // 2.8 editors kept the sudo password inside terminal_config. It is only - // ever handed out as sudoPassword, and only to the owner. - const { sudoPassword: legacySudo, ...rest } = host.terminalConfig as Record< - string, - unknown - >; - if (ownerEquivalent && !host.sudoPassword && legacySudo) { - host.sudoPassword = legacySudo; - } - host.terminalConfig = rest; - } // Stored as the text "true"/"false"; the string "false" is truthy and would // make the password provider skip password auth. host.forceKeyboardInteractive = String(host.forceKeyboardInteractive) === "true"; - // A row the boot copy has not reached yet still has them in terminal_config. - host.sshOptions = parseSshOptions( - host.sshOptions != null ? host.sshOptions : host.terminalConfig, - ); + host.sshOptions = parseSshOptions(host.sshOptions); if (typeof host.socks5ProxyChain === "string" && host.socks5ProxyChain) { try { host.socks5ProxyChain = JSON.parse(host.socks5ProxyChain as string); @@ -157,25 +131,8 @@ export async function resolveHostById( sharedAuthResolution = await resolveRecipientSshAuth(host, hostId, userId); if (!sharedAuthResolution) return null; } else { - let effectiveCredentialId = host.credentialId as number | null | undefined; - if ( - !effectiveCredentialId && - host.authType === "credential" && - host.folder - ) { - try { - effectiveCredentialId = await repository.findFolderCredentialId( - ownerId, - host.folder as string, - ); - } catch (e) { - sshLogger.warn("Failed to resolve folder credential for host", { - operation: "host_resolver_folder_credential", - hostId, - error: getErrorMessage(e, "Unknown"), - }); - } - } + const effectiveCredentialId = host.credentialId as + number | null | undefined; if (effectiveCredentialId) { try { diff --git a/src/backend/hosts/ssh-options.ts b/src/backend/hosts/ssh-options.ts index 02b90d4cf..241fd2634 100644 --- a/src/backend/hosts/ssh-options.ts +++ b/src/backend/hosts/ssh-options.ts @@ -50,8 +50,7 @@ function optionalString(value: unknown): string | null | undefined { /** * The SSH options in a value (a JSON string or an object), typed and with - * anything else dropped. A terminal_config value works too, which is how the - * 2.8 keys are read. + * anything else dropped. A 2.8 client's terminalConfig works too. */ export function parseSshOptions(value: unknown): HostSshOptions { const source = asObject(value); diff --git a/src/backend/plugins/ctx-ssh-auth.ts b/src/backend/plugins/ctx-ssh-auth.ts index c09022ec1..37228419c 100644 --- a/src/backend/plugins/ctx-ssh-auth.ts +++ b/src/backend/plugins/ctx-ssh-auth.ts @@ -601,12 +601,13 @@ export function toCoreIdentity( returnTo: identity.returnTo, rememberMe: identity.rememberMe, legacyIdentifier: identity.legacy?.identifier, - ssoProviderId: - identity.logoutClaims?.providerId ?? - identity.legacy?.providerRowId ?? - null, - oidcSub: identity.logoutClaims?.sub ?? null, - oidcSid: identity.logoutClaims?.sid ?? null, + externalSession: identity.logoutClaims + ? { + providerId: identity.logoutClaims.providerId ?? null, + sub: identity.logoutClaims.sub ?? null, + sid: identity.logoutClaims.sid ?? null, + } + : null, roleSync: identity.roles ? { desired: [...identity.roles.desired], @@ -864,7 +865,7 @@ export function createPluginAuth({ manifest, bag, audit }: Deps): PluginAuth { const { AuthManager } = await import("../utils/auth-manager.js"); const revoked = await AuthManager.getInstance().revokeSessionsByExternalSession({ - ssoProviderId: match.providerId ?? null, + providerId: match.providerId ?? null, sub: match.sub ?? null, sid: match.sid ?? null, }); diff --git a/src/backend/starter.ts b/src/backend/starter.ts index 0e862dd44..cf654bb19 100644 --- a/src/backend/starter.ts +++ b/src/backend/starter.ts @@ -58,15 +58,6 @@ async function provisionLocalDesktopUserIfNeeded(): Promise { id, username: "local", passwordHash: "", - isOidc: false, - clientId: "", - clientSecret: "", - issuerUrl: "", - authorizationUrl: "", - tokenUrl: "", - identifierPath: "", - namePath: "", - scopes: "openid email profile", }); try { @@ -191,7 +182,7 @@ async function provisionLocalDesktopUserIfNeeded(): Promise { createCurrentUserAuthRepository().listUserIdsWithSecondFactors(), ]); const conflictingUser = users.some( - (user) => user.isOidc || secondFactorUsers.has(user.id), + (user) => user.isExternal || secondFactorUsers.has(user.id), ); if (process.env.OIDC_CLIENT_ID || conflictingUser) { throw new Error( diff --git a/src/backend/sync/entities.ts b/src/backend/sync/entities.ts index f7b8cbb8d..716754530 100644 --- a/src/backend/sync/entities.ts +++ b/src/backend/sync/entities.ts @@ -67,11 +67,10 @@ const HOST_LOCAL_FIELDS = [ ]; /** - * Columns kept until 26.10.0 that nothing reads any more: their values moved - * into plugin host settings, which sync with the host's pluginSettings. They - * stay off the wire so a stale local id never reaches the other side. + * Columns 26.10.0 dropped. A 2.9 desktop still sends them; they never reach + * the table. */ -const HOST_RETIRED_FIELDS = ["quickActions"]; +const HOST_RETIRED_FIELDS = ["quickActions", "terminalConfig"]; let registered = false; diff --git a/src/backend/tests/auth/auth-test-helpers.ts b/src/backend/tests/auth/auth-test-helpers.ts index b56bcf3b0..54d945ddb 100644 --- a/src/backend/tests/auth/auth-test-helpers.ts +++ b/src/backend/tests/auth/auth-test-helpers.ts @@ -5,9 +5,6 @@ export interface FakeUser { username: string; passwordHash: string; isAdmin: boolean; - isOidc: boolean; - oidcIdentifier?: string | null; - ssoProviderId?: number | null; } /** In-memory stand-ins for the repositories the login pipeline touches. */ @@ -37,18 +34,28 @@ export function createAuthState() { export type AuthState = ReturnType; +/** The user as UserRepository returns it, with isExternal filled in. */ +function withIdentities(state: AuthState, user: FakeUser | undefined) { + if (!user) return null; + return { + ...user, + isExternal: state.identities.some((row) => row.userId === user.id), + }; +} + export function fakeFactory(state: AuthState) { return { createCurrentUserRepository: () => ({ - findById: async (id: string) => state.users.get(id) ?? null, + findById: async (id: string) => + withIdentities(state, state.users.get(id)), findByUsername: async (username: string) => - [...state.users.values()].find((u) => u.username === username) ?? null, - findByExternalIdentifier: async (identifier: string) => - [...state.users.values()].find( - (u) => u.oidcIdentifier === identifier, - ) ?? null, + withIdentities( + state, + [...state.users.values()].find((u) => u.username === username), + ), countAll: async () => state.users.size, - listAll: async () => [...state.users.values()], + listAll: async () => + [...state.users.values()].map((u) => withIdentities(state, u)), createFirstSsoUser: async (user: FakeUser) => { const isFirstUser = state.users.size === 0; const created = { diff --git a/src/backend/tests/auth/login-pipeline.test.ts b/src/backend/tests/auth/login-pipeline.test.ts index ad9c4f412..bfb221db3 100644 --- a/src/backend/tests/auth/login-pipeline.test.ts +++ b/src/backend/tests/auth/login-pipeline.test.ts @@ -119,7 +119,6 @@ function addUser( username: "alice", passwordHash: bcrypt.hashSync(PASSWORD, 4), isAdmin: false, - isOidc: false, ...overrides, }; h.state.users.set(user.id, user); @@ -461,7 +460,7 @@ describe("external identities", () => { email: "a@example.com", name: "Alice", legacyIdentifier: "sub-1", - ssoProviderId: 3, + externalSession: { providerId: 3, sub: "sub-1", sid: "sid-1" }, }; // The sso plugin's method, which marks itself external. @@ -487,7 +486,6 @@ describe("external identities", () => { expect(user).toMatchObject({ username: "Alice", isAdmin: true, - isOidc: true, }); expect(h.state.identities).toEqual([ expect.objectContaining({ @@ -511,7 +509,7 @@ describe("external identities", () => { }), ).rejects.toMatchObject({ code: "registration_disabled" }); - h.state.settings.set("oidc_auto_provision", "true"); + h.state.settings.set("external_auto_provision", "true"); await expect( runLogin( fakeRequest() as never, @@ -521,45 +519,11 @@ describe("external identities", () => { ).rejects.toMatchObject({ code: "user_not_allowed" }); }); - it.each(["true", "false"])( - "does not duplicate an unscoped migrated account with provisioning %s", - async (enabled) => { - addUser({ - id: "old", - username: "Alice", - isOidc: true, - passwordHash: "", - oidcIdentifier: "sub-1", - ssoProviderId: null, - }); - const { runExternalIdentityMigration } = - await import("../../upgrade/external-identity-migration.js"); - await runExternalIdentityMigration(); - h.state.settings.set("oidc_auto_provision", enabled); - await expect( - runLogin(fakeRequest() as never, external, { - methodId: "oidc", - rememberMe: false, - }), - ).rejects.toMatchObject({ code: "legacy_identity_conflict" }); - expect([...h.state.users.keys()]).toEqual(["old"]); - expect(h.state.identities).toHaveLength(1); - expect(h.state.identities[0]).toMatchObject({ - userId: "old", - providerId: "legacy-oidc", - subject: "sub-1", - }); - }, - ); - it("keeps equal subjects on explicitly different providers separate", async () => { addUser({ id: "other", username: "Other Alice", - isOidc: true, passwordHash: "", - oidcIdentifier: "sub-1", - ssoProviderId: 9, }); h.state.identities.push({ id: 1, @@ -568,7 +532,7 @@ describe("external identities", () => { subject: "sub-1", email: null, }); - h.state.settings.set("oidc_auto_provision", "true"); + h.state.settings.set("external_auto_provision", "true"); const result = await runLogin(fakeRequest() as never, external, { methodId: "oidc", rememberMe: false, @@ -584,10 +548,7 @@ describe("external identities", () => { addUser({ id: "old", username: "Alice", - isOidc: true, passwordHash: "", - oidcIdentifier: "sub-1", - ssoProviderId: null, }); h.state.identities.push({ id: 1, @@ -605,7 +566,7 @@ describe("external identities", () => { }); it("re-checks the allowed list for existing users", async () => { - addUser({ id: "u9", username: "Alice", isOidc: true, passwordHash: "" }); + addUser({ id: "u9", username: "Alice", passwordHash: "" }); h.state.identities.push({ id: 1, userId: "u9", @@ -622,42 +583,10 @@ describe("external identities", () => { ).rejects.toMatchObject({ code: "user_not_allowed" }); }); - it("finds an account from before the identity table by its old identifier", async () => { - addUser({ - id: "old", - username: "ldap-bob", - isOidc: true, - passwordHash: "", - oidcIdentifier: "ldap:4:bob", - }); - const result = await runLogin( - fakeRequest() as never, - { - kind: "external", - provider: "4", - subject: "bob", - name: "Bob", - legacyIdentifier: "ldap:4:bob", - ssoProviderId: 4, - }, - { methodId: "ldap", rememberMe: false }, - ); - expect(result.kind).toBe("session"); - expect(h.state.users.size).toBe(1); - expect(h.state.identities).toEqual([ - expect.objectContaining({ - userId: "old", - providerId: "4", - subject: "bob", - }), - ]); - }); - it("keeps admin in step with the provider's admin group", async () => { addUser({ id: "u9", username: "Alice", - isOidc: true, passwordHash: "", isAdmin: true, }); @@ -680,7 +609,6 @@ describe("external identities", () => { addUser({ id: "u9", username: "Alice", - isOidc: true, passwordHash: "", }); h.state.factors.push({ userId: "u9", pluginId: "totp", factorId: "totp" }); @@ -703,7 +631,6 @@ describe("external identities", () => { addUser({ id: "u9", username: "Alice", - isOidc: true, passwordHash: "", }); h.state.factors.push({ userId: "u9", pluginId: "totp", factorId: "totp" }); @@ -729,9 +656,7 @@ describe("redirect logins", () => { subject: "sub-1", name: "Alice", returnTo: "https://termix.example", - ssoProviderId: 3, - oidcSub: "sub-1", - oidcSid: "sid-1", + externalSession: { providerId: 3, sub: "sub-1", sid: "sid-1" }, }; it("sets the cookie and redirects with success, carrying the SSO claims", async () => { @@ -748,9 +673,7 @@ describe("redirect logins", () => { expect(h.manager.generateJWTToken).toHaveBeenCalledWith( expect.any(String), expect.objectContaining({ - oidcSub: "sub-1", - oidcSid: "sid-1", - ssoProviderId: 3, + externalSession: { providerId: 3, sub: "sub-1", sid: "sid-1" }, }), ); }); @@ -774,7 +697,6 @@ describe("redirect logins", () => { addUser({ id: "u9", username: "Alice", - isOidc: true, passwordHash: "", }); h.state.factors.push({ userId: "u9", pluginId: "totp", factorId: "totp" }); @@ -812,7 +734,9 @@ describe("redirect logins", () => { expect(verify.statusCode).toBe(200); expect(h.manager.generateJWTToken).toHaveBeenLastCalledWith( "u9", - expect.objectContaining({ oidcSid: "sid-1", ssoProviderId: 3 }), + expect.objectContaining({ + externalSession: { providerId: 3, sub: "sub-1", sid: "sid-1" }, + }), ); }); diff --git a/src/backend/tests/database/db/multi-dialect.test.ts b/src/backend/tests/database/db/multi-dialect.test.ts index d716958c4..5fda37253 100644 --- a/src/backend/tests/database/db/multi-dialect.test.ts +++ b/src/backend/tests/database/db/multi-dialect.test.ts @@ -113,8 +113,8 @@ describe("generated schemas", () => { expect(auditFks).toHaveLength(1); expect(auditFks[0].onDelete).toBe("set null"); - const folderFks = read(schema.sshFolders).foreignKeys; - expect(folderFks.map((fk) => fk.onDelete).sort()).toEqual([ + const hostFks = read(schema.hosts).foreignKeys; + expect([...new Set(hostFks.map((fk) => fk.onDelete))].sort()).toEqual([ "cascade", "set null", ]); diff --git a/src/backend/tests/database/db/retire-2-8-columns.test.ts b/src/backend/tests/database/db/retire-2-8-columns.test.ts new file mode 100644 index 000000000..fad97365a --- /dev/null +++ b/src/backend/tests/database/db/retire-2-8-columns.test.ts @@ -0,0 +1,199 @@ +import fs from "fs"; +import os from "os"; +import path from "path"; +import Database from "better-sqlite3"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +/** + * A database 2.9 left behind still has the 2.8 columns it kept for a + * downgrade. Opening it in 26.10.0 drops them, keeps what still matters + * (a sudo password kept in terminal_config, an SSO sign-in, the hosts and + * folders themselves) and removes the settings rows the plugins copied. + */ +describe("dropping the columns 2.9.0 kept", () => { + let dataDir: string; + + beforeEach(() => { + dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "termix-retire-")); + vi.resetModules(); + process.env.DATA_DIR = dataDir; + process.env.DB_FILE_ENCRYPTION = "false"; + process.env.ALLOW_EMPTY_DATA_DIR = "true"; + }); + + afterEach(() => { + delete process.env.DATA_DIR; + delete process.env.DB_FILE_ENCRYPTION; + delete process.env.ALLOW_EMPTY_DATA_DIR; + fs.rmSync(dataDir, { recursive: true, force: true }); + }); + + function write29Database(): void { + const seed = new Database(":memory:"); + seed.exec(` + CREATE TABLE plugins ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + version TEXT NOT NULL, + tier TEXT NOT NULL DEFAULT 'available', + source TEXT NOT NULL DEFAULT 'community', + registry_id TEXT, + state TEXT NOT NULL DEFAULT 'disabled', + last_error TEXT, + installed_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + auto_update INTEGER NOT NULL DEFAULT 0, + manifest_json TEXT NOT NULL + ); + + CREATE TABLE users ( + id TEXT PRIMARY KEY, + username TEXT NOT NULL, + password_hash TEXT NOT NULL, + is_oidc INTEGER NOT NULL DEFAULT 0, + oidc_identifier TEXT, + client_secret TEXT + ); + + CREATE TABLE settings (key TEXT PRIMARY KEY, value TEXT NOT NULL); + + CREATE TABLE ssh_credentials ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id TEXT NOT NULL, + name TEXT NOT NULL, + auth_type TEXT NOT NULL, + username TEXT + ); + + CREATE TABLE ssh_data ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id TEXT NOT NULL, + name TEXT, + ip TEXT NOT NULL, + port INTEGER NOT NULL, + username TEXT NOT NULL, + auth_type TEXT NOT NULL DEFAULT 'password', + sudo_password TEXT, + terminal_config TEXT, + quick_actions TEXT, + tunnel_connections TEXT, + enable_docker INTEGER NOT NULL DEFAULT 0, + stats_config TEXT, + rdp_user TEXT, + created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + + CREATE TABLE ssh_folders ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id TEXT NOT NULL, + name TEXT NOT NULL, + color TEXT, + credential_id INTEGER REFERENCES ssh_credentials(id) ON DELETE SET NULL, + created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE + ); + + CREATE TABLE user_preferences ( + user_id TEXT PRIMARY KEY, + theme TEXT, + terminal_defaults TEXT, + rdp_defaults TEXT, + updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + + INSERT INTO users (id, username, password_hash, is_oidc, oidc_identifier) + VALUES ('u1', 'alice', 'hash', 1, 'sub-1'); + INSERT INTO ssh_credentials (id, user_id, name, auth_type) + VALUES (5, 'u1', 'cred', 'password'); + INSERT INTO ssh_data (id, user_id, name, ip, port, username, + terminal_config, tunnel_connections, enable_docker, rdp_user) + VALUES + (1, 'u1', 'web', '10.0.0.1', 22, '$oidc.preferred_username', + '{"sudoPassword":"s3cret","theme":"nord"}', '[]', 1, 'admin'), + (2, 'u1', 'db', '10.0.0.2', 22, 'root', NULL, NULL, 0, NULL); + INSERT INTO ssh_folders (id, user_id, name, color, credential_id) + VALUES (3, 'u1', 'prod', '#fff', 5); + INSERT INTO user_preferences (user_id, theme, terminal_defaults, rdp_defaults) + VALUES ('u1', 'dark', '{}', '{}'); + INSERT INTO settings (key, value) VALUES + ('guac_url', 'guacd:4822'), + ('analytics_enabled', 'true'), + ('allow_registration', 'false'); + `); + fs.writeFileSync(path.join(dataDir, "db.sqlite"), seed.serialize()); + seed.close(); + } + + const columns = (sqlite: Database.Database, table: string) => + (sqlite.prepare(`PRAGMA table_info(${table})`).all() as Array<{ + name: string; + }>).map((col) => col.name); + + it("drops the columns and rows and keeps the data that still matters", async () => { + write29Database(); + const db = await import("../../../database/db/index.js"); + await db.initializeDatabase(); + const sqlite = db.getSqlite(); + + expect(columns(sqlite, "users")).not.toContain("oidc_identifier"); + expect(columns(sqlite, "users")).not.toContain("is_oidc"); + expect(columns(sqlite, "ssh_data")).not.toEqual( + expect.arrayContaining(["terminal_config"]), + ); + for (const gone of [ + "terminal_config", + "quick_actions", + "tunnel_connections", + "enable_docker", + "stats_config", + ]) { + expect(columns(sqlite, "ssh_data")).not.toContain(gone); + } + // A login moved only once its owner signs in stays until then. + expect(columns(sqlite, "ssh_data")).toContain("rdp_user"); + expect(columns(sqlite, "ssh_folders")).not.toContain("credential_id"); + expect(columns(sqlite, "user_preferences")).not.toContain("rdp_defaults"); + expect(columns(sqlite, "user_preferences")).not.toContain( + "terminal_defaults", + ); + + const hosts = sqlite + .prepare("SELECT id, username, sudo_password FROM ssh_data ORDER BY id") + .all(); + expect(hosts).toEqual([ + { id: 1, username: "$external.username", sudo_password: "s3cret" }, + { id: 2, username: "root", sudo_password: null }, + ]); + expect( + sqlite.prepare("SELECT name, color FROM ssh_folders").all(), + ).toEqual([{ name: "prod", color: "#fff" }]); + expect( + sqlite.prepare("SELECT theme FROM user_preferences").get(), + ).toEqual({ theme: "dark" }); + + const keys = ( + sqlite.prepare("SELECT key FROM settings").all() as Array<{ + key: string; + }> + ).map((row) => row.key); + expect(keys).toContain("allow_registration"); + expect(keys).not.toContain("guac_url"); + expect(keys).not.toContain("analytics_enabled"); + }); + + it("refuses a 2.8 database", async () => { + const seed = new Database(":memory:"); + seed.exec(` + CREATE TABLE users (id TEXT PRIMARY KEY, username TEXT NOT NULL, + password_hash TEXT NOT NULL); + CREATE TABLE ssh_data (id INTEGER PRIMARY KEY, user_id TEXT NOT NULL); + `); + fs.writeFileSync(path.join(dataDir, "db.sqlite"), seed.serialize()); + seed.close(); + + const db = await import("../../../database/db/index.js"); + await expect(db.initializeDatabase()).rejects.toThrow(/2\.8 or older/); + }); +}); diff --git a/src/backend/tests/database/db/retired-columns.test.ts b/src/backend/tests/database/db/retired-columns.test.ts new file mode 100644 index 000000000..d2031927e --- /dev/null +++ b/src/backend/tests/database/db/retired-columns.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from "vitest"; +import { + legacySudoMoves, + legacySudoPassword, +} from "../../../database/db/retired-columns.js"; + +describe("legacySudoPassword", () => { + it("reads the sudo password out of a terminal_config value", () => { + expect(legacySudoPassword('{"sudoPassword":"s3cret"}')).toBe("s3cret"); + expect(legacySudoPassword({ sudoPassword: "s3cret" })).toBe("s3cret"); + expect(legacySudoPassword(JSON.stringify('{"sudoPassword":"x"}'))).toBe( + "x", + ); + }); + + it("finds nothing in anything else", () => { + expect(legacySudoPassword(null)).toBeNull(); + expect(legacySudoPassword("{")).toBeNull(); + expect(legacySudoPassword('{"sudoPassword":""}')).toBeNull(); + expect(legacySudoPassword("[1]")).toBeNull(); + }); +}); + +describe("legacySudoMoves", () => { + it("moves only hosts with no sudo password of their own", () => { + expect( + legacySudoMoves([ + { id: 1, terminal_config: '{"sudoPassword":"a"}', sudo_password: null }, + { id: 2, terminal_config: '{"sudoPassword":"b"}', sudo_password: "own" }, + { id: 3, terminal_config: '{"theme":"nord"}', sudo_password: null }, + ]), + ).toEqual([{ id: 1, sudoPassword: "a" }]); + }); +}); diff --git a/src/backend/tests/database/db/share-ssh-auth-backfill.test.ts b/src/backend/tests/database/db/share-ssh-auth-backfill.test.ts index bf7920335..d713831d3 100644 --- a/src/backend/tests/database/db/share-ssh-auth-backfill.test.ts +++ b/src/backend/tests/database/db/share-ssh-auth-backfill.test.ts @@ -33,10 +33,28 @@ describe("share_ssh_auth backfill", () => { fs.rmSync(dataDir, { recursive: true, force: true }); }); - /** A 2.6.0 database: hosts and shares exist, the column does not. */ + /** + * Hosts and shares exist, the column does not. The plugins table is there + * because 26.10.0 only opens a database 2.9 has already upgraded. + */ function writePreUpgradeDatabase(): void { const seed = new Database(":memory:"); seed.exec(` + CREATE TABLE plugins ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + version TEXT NOT NULL, + tier TEXT NOT NULL DEFAULT 'available', + source TEXT NOT NULL DEFAULT 'community', + registry_id TEXT, + state TEXT NOT NULL DEFAULT 'disabled', + last_error TEXT, + installed_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + auto_update INTEGER NOT NULL DEFAULT 0, + manifest_json TEXT NOT NULL + ); + CREATE TABLE users ( id TEXT PRIMARY KEY, username TEXT NOT NULL, diff --git a/src/backend/tests/database/db/upgrade-guard.test.ts b/src/backend/tests/database/db/upgrade-guard.test.ts new file mode 100644 index 000000000..accafd451 --- /dev/null +++ b/src/backend/tests/database/db/upgrade-guard.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from "vitest"; +import { + assertNotPre29Database, + PRE_29_DATABASE_ERROR, +} from "../../../database/db/upgrade-guard.js"; + +const tables = (...names: string[]) => (name: string) => names.includes(name); + +describe("assertNotPre29Database", () => { + it("refuses a 2.8 database, which has hosts but no plugins table", async () => { + await expect(assertNotPre29Database(tables("ssh_data"))).rejects.toThrow( + PRE_29_DATABASE_ERROR, + ); + }); + + it("opens a database 2.9 has upgraded", async () => { + await expect( + assertNotPre29Database(tables("ssh_data", "plugins")), + ).resolves.toBeUndefined(); + }); + + it("opens a new database", async () => { + await expect(assertNotPre29Database(tables())).resolves.toBeUndefined(); + }); +}); diff --git a/src/backend/tests/database/repositories/host-folder-repository.test.ts b/src/backend/tests/database/repositories/host-folder-repository.test.ts index 99f04c839..889948868 100644 --- a/src/backend/tests/database/repositories/host-folder-repository.test.ts +++ b/src/backend/tests/database/repositories/host-folder-repository.test.ts @@ -89,7 +89,6 @@ describe("HostFolderRepository", () => { "prod", "#abcdef", "folder", - undefined, "2026-02-01T00:00:00.000Z", ), ).resolves.toMatchObject({ @@ -102,7 +101,6 @@ describe("HostFolderRepository", () => { "new", null, null, - null, "2026-03-01T00:00:00.000Z", ), ).resolves.toMatchObject({ @@ -112,28 +110,6 @@ describe("HostFolderRepository", () => { expect(writes).toBe(2); }); - it("assigns a credential to a folder and resolves it for nested paths", async () => { - const { repository } = await createRepository(); - - await expect( - repository.upsertMetadata( - "user-1", - "prod", - undefined, - undefined, - 1, - "2026-02-01T00:00:00.000Z", - ), - ).resolves.toMatchObject({ - created: false, - folder: { credentialId: 1 }, - }); - - const folders = await repository.listFolders("user-1"); - const prodFolder = folders.find((f) => f.name === "prod"); - expect(prodFolder?.credentialId).toBe(1); - }); - it("lists and deletes hosts and folder records in a folder tree", async () => { let writes = 0; const { repository } = await createRepository(() => { diff --git a/src/backend/tests/database/repositories/host-resolution-repository.test.ts b/src/backend/tests/database/repositories/host-resolution-repository.test.ts index 318567351..38d28d4ee 100644 --- a/src/backend/tests/database/repositories/host-resolution-repository.test.ts +++ b/src/backend/tests/database/repositories/host-resolution-repository.test.ts @@ -44,11 +44,6 @@ describe("HostResolutionRepository", () => { (1, 'user-1', 'web', '10.0.0.1', 22, 'root', 'password', 7, '[{"autoStart":true}]'), (2, 'user-1', 'db', '10.0.0.2', 22, 'admin', 'none', NULL, NULL), (3, 'user-2', 'other', '10.0.0.3', 22, 'root', 'none', NULL, '[{"autoStart":false}]'); - INSERT INTO ssh_folders (user_id, name, credential_id) - VALUES - ('user-1', 'switches', 7), - ('user-1', 'switches / floor1', NULL), - ('user-1', 'no-cred', NULL); INSERT INTO host_access ( host_id, user_id, granted_by, permission_level ) @@ -330,26 +325,6 @@ describe("HostResolutionRepository", () => { ).resolves.toBeNull(); }); - it("resolves a folder's assigned credential, walking up to parent folders", async () => { - const repository = await createRepository(); - - await expect( - repository.findFolderCredentialId("user-1", "switches"), - ).resolves.toBe(7); - await expect( - repository.findFolderCredentialId("user-1", "switches / floor1"), - ).resolves.toBe(7); - await expect( - repository.findFolderCredentialId("user-1", "no-cred"), - ).resolves.toBeNull(); - await expect( - repository.findFolderCredentialId("user-1", "unknown"), - ).resolves.toBeNull(); - await expect( - repository.findFolderCredentialId("user-1", ""), - ).resolves.toBeNull(); - }); - describe("listCredentialsByIdsForUser", () => { it("returns the owner's credentials keyed by id", async () => { const repository = await createRepository(); diff --git a/src/backend/tests/database/repositories/rbac-access-repository.test.ts b/src/backend/tests/database/repositories/rbac-access-repository.test.ts index cdb5bbd40..1b26efda7 100644 --- a/src/backend/tests/database/repositories/rbac-access-repository.test.ts +++ b/src/backend/tests/database/repositories/rbac-access-repository.test.ts @@ -19,11 +19,11 @@ describe("RbacAccessRepository", () => { adapter = new TestSqliteDatabase(); const context = await adapter.connect(); await adapter.exec(` - INSERT INTO users (id, username, password_hash, is_admin, is_oidc) + INSERT INTO users (id, username, password_hash, is_admin) VALUES - ('admin', 'admin', 'hash', 1, 0), - ('user-1', 'alice', 'hash', 0, 0), - ('owner-1', 'owner', 'hash', 0, 0); + ('admin', 'admin', 'hash', 1), + ('user-1', 'alice', 'hash', 0), + ('owner-1', 'owner', 'hash', 0); INSERT INTO roles (id, name, display_name, is_system) VALUES (7, 'ops', 'Operations', 0); INSERT INTO ssh_credentials (id, user_id, name, username, auth_type) VALUES diff --git a/src/backend/tests/database/repositories/role-repository.test.ts b/src/backend/tests/database/repositories/role-repository.test.ts index 963c5953f..38a23ed06 100644 --- a/src/backend/tests/database/repositories/role-repository.test.ts +++ b/src/backend/tests/database/repositories/role-repository.test.ts @@ -18,8 +18,8 @@ describe("RoleRepository", () => { adapter = new TestSqliteDatabase(); const context = await adapter.connect(); await adapter.exec(` - INSERT INTO users (id, username, password_hash, is_admin, is_oidc) - VALUES ('admin', 'admin', 'hash', 1, 0), ('user-1', 'user', 'hash', 0, 0); + INSERT INTO users (id, username, password_hash, is_admin) + VALUES ('admin', 'admin', 'hash', 1), ('user-1', 'user', 'hash', 0); `); return new RoleRepository(context, onWrite); diff --git a/src/backend/tests/database/repositories/user-preference-repository.test.ts b/src/backend/tests/database/repositories/user-preference-repository.test.ts index febf757c5..526034907 100644 --- a/src/backend/tests/database/repositories/user-preference-repository.test.ts +++ b/src/backend/tests/database/repositories/user-preference-repository.test.ts @@ -34,19 +34,19 @@ describe("UserPreferenceRepository", () => { reopenTabsOnLogin: true, theme: "dark", storageMode: "local", - commandAutocomplete: true, + commandPaletteEnabled: true, }); expect(created).toMatchObject({ userId: "user-1", reopenTabsOnLogin: true, theme: "dark", storageMode: "local", - commandAutocomplete: true, + commandPaletteEnabled: true, }); const updated = await repo.upsert("user-1", { theme: "light", - commandAutocomplete: false, + commandPaletteEnabled: false, updatedAt: "2026-06-27T00:00:00.000Z", }); expect(updated).toMatchObject({ @@ -54,7 +54,7 @@ describe("UserPreferenceRepository", () => { reopenTabsOnLogin: true, theme: "light", storageMode: "local", - commandAutocomplete: false, + commandPaletteEnabled: false, }); }); diff --git a/src/backend/tests/database/repositories/user-session-repositories.test.ts b/src/backend/tests/database/repositories/user-session-repositories.test.ts index 729169b51..269de0038 100644 --- a/src/backend/tests/database/repositories/user-session-repositories.test.ts +++ b/src/backend/tests/database/repositories/user-session-repositories.test.ts @@ -56,7 +56,6 @@ describe("UserRepository and SessionRepository", () => { username: "admin", passwordHash: "hash", isAdmin: true, - isOidc: false, }); expect(await repo.users.countAdmins()).toBe(1); @@ -69,13 +68,15 @@ describe("UserRepository and SessionRepository", () => { ).toEqual(["user-1"]); expect(await repo.users.listByIds([])).toEqual([]); - const updated = await repo.users.update("user-1", { - oidcIdentifier: "oidc:admin", - }); - expect(updated?.oidcIdentifier).toBe("oidc:admin"); - expect((await repo.users.findByExternalIdentifier("oidc:admin"))?.id).toBe( - "user-1", - ); + const updated = await repo.users.update("user-1", { isAdmin: false }); + expect(updated).toMatchObject({ isAdmin: false, isExternal: false }); + + await adapter!.exec(` + INSERT INTO user_external_identities (user_id, provider_id, subject) + VALUES ('user-1', '3', 'sub-1'); + `); + expect((await repo.users.findById("user-1"))?.isExternal).toBe(true); + expect((await repo.users.listAll())[0].isExternal).toBe(true); expect(await repo.users.delete("user-1")).toBe(true); expect(await repo.users.findById("user-1")).toBeNull(); @@ -88,13 +89,11 @@ describe("UserRepository and SessionRepository", () => { id: "user-1", username: "first", passwordHash: "hash", - isOidc: false, }); const second = await repo.users.createFirstLocalUser({ id: "user-2", username: "second", passwordHash: "hash", - isOidc: false, }); expect(first.isFirstUser).toBe(true); @@ -112,27 +111,18 @@ describe("UserRepository and SessionRepository", () => { username: "first", passwordHash: "", isAdmin: false, - isOidc: true, - oidcIdentifier: "ldap:provider:first", - ssoProviderId: 1, }); const providerAdmin = await repo.users.createFirstSsoUser({ id: "user-2", username: "provider-admin", passwordHash: "", isAdmin: true, - isOidc: true, - oidcIdentifier: "ldap:provider:admin", - ssoProviderId: 1, }); const regular = await repo.users.createFirstSsoUser({ id: "user-3", username: "regular", passwordHash: "", isAdmin: false, - isOidc: true, - oidcIdentifier: "ldap:provider:regular", - ssoProviderId: 1, }); expect(first.isFirstUser).toBe(true); @@ -157,7 +147,6 @@ describe("UserRepository and SessionRepository", () => { username: "admin", passwordHash: "hash", isAdmin: true, - isOidc: false, }); await repo.users.update("user-1", { isAdmin: false }); await repo.users.delete("user-1"); @@ -172,7 +161,6 @@ describe("UserRepository and SessionRepository", () => { username: "user", passwordHash: "hash", isAdmin: false, - isOidc: false, }); await repo.sessions.create({ @@ -208,7 +196,6 @@ describe("UserRepository and SessionRepository", () => { username: "user", passwordHash: "hash", isAdmin: false, - isOidc: false, }); await repo.sessions.create({ id: "session-1", @@ -250,7 +237,6 @@ describe("UserRepository and SessionRepository", () => { username: "user", passwordHash: "hash", isAdmin: false, - isOidc: false, }); await repo.sessions.create({ id: "session-1", @@ -281,7 +267,6 @@ describe("UserRepository and SessionRepository", () => { username: "user", passwordHash: "hash", isAdmin: false, - isOidc: false, }); for (const id of ["keep", "drop-1", "drop-2"]) { @@ -308,7 +293,6 @@ describe("UserRepository and SessionRepository", () => { username: "user", passwordHash: "hash", isAdmin: false, - isOidc: false, }); await repo.sessions.create({ diff --git a/src/backend/tests/database/routes/host-import-terminal-settings.test.ts b/src/backend/tests/database/routes/host-import-terminal-settings.test.ts index 1a2000d02..fa999baf6 100644 --- a/src/backend/tests/database/routes/host-import-terminal-settings.test.ts +++ b/src/backend/tests/database/routes/host-import-terminal-settings.test.ts @@ -96,9 +96,8 @@ it.each([false, true])( overwrite ? 19 : 71, expect.objectContaining({ pluginSettings: host.pluginSettings }), ); - expect(JSON.parse(saved.terminalConfig)).toEqual({ - macOptionIsMeta: false, - }); + // The 2.8 terminalConfig reaches the plugin, never a host column. + expect(saved).not.toHaveProperty("terminalConfig"); }, ); diff --git a/src/backend/tests/database/routes/host-normalizers.test.ts b/src/backend/tests/database/routes/host-normalizers.test.ts index 29d995029..6a0655b23 100644 --- a/src/backend/tests/database/routes/host-normalizers.test.ts +++ b/src/backend/tests/database/routes/host-normalizers.test.ts @@ -284,7 +284,7 @@ describe("stripSensitiveFields", () => { expect(result.hasKey).toBe(false); }); - it("detects sudo password stored only in nested terminalConfig", () => { + it("strips a sudo password nested in terminalConfig", () => { const result = stripSensitiveFields({ name: "web", terminalConfig: { @@ -292,7 +292,7 @@ describe("stripSensitiveFields", () => { sudoPassword: "nested-only-sudo", }, }); - expect(result.hasSudoPassword).toBe(true); + expect(result.hasSudoPassword).toBe(false); expect( (result.terminalConfig as Record).sudoPassword, ).toBeUndefined(); @@ -483,25 +483,6 @@ describe("transformHostResponse terminal fields", () => { expect(host.terminalConfig).toEqual({ keepaliveInterval: 20 }); }); - it("reads the options out of terminal_config before the boot copy", () => { - const host = row({ - sshOptions: null, - terminalConfig: JSON.stringify({ agentForwarding: true }), - }); - expect(host.sshOptions).toEqual({ agentForwarding: true }); - }); - - it("surfaces a 2.8 sudo password for the sanitizers to strip", () => { - const host = row({ - sudoPassword: null, - terminalConfig: JSON.stringify({ sudoPassword: "legacy" }), - }); - const stripped = stripSensitiveFields(host); - expect(stripped.hasSudoPassword).toBe(true); - expect(stripped).not.toHaveProperty("sudoPassword"); - expect(JSON.stringify(stripped)).not.toContain("legacy"); - }); - it("hides the owner's agent socket from a shared recipient", () => { const shared = sanitizeHostForRecipient( row({ diff --git a/src/backend/tests/database/routes/host-terminal-fields.test.ts b/src/backend/tests/database/routes/host-terminal-fields.test.ts index bb062fbb1..a4120a5d9 100644 --- a/src/backend/tests/database/routes/host-terminal-fields.test.ts +++ b/src/backend/tests/database/routes/host-terminal-fields.test.ts @@ -10,17 +10,11 @@ vi.mock("../../../database/repositories/factory.js", () => ({ }), })); -const { mergeStoredTerminalFields, parseTerminalConfig } = +const { mergeStoredSshOptions, parseTerminalConfig } = await import("../../../database/routes/host-terminal-fields.js"); beforeEach(() => { state.stored = { - terminalConfig: JSON.stringify({ - theme: "nord", - sudoPassword: "legacy-sudo", - agentSocketPath: "/owner/agent", - startupSnippetId: 1, - }), sshOptions: JSON.stringify({ agentSocketPath: "/owner/agent" }), }; }); @@ -31,10 +25,10 @@ async function merge( sshOptions?: string, ) { const row: Record = { - terminalConfig: "replaced wholesale", + terminalConfig: "from the payload", ...(sshOptions !== undefined ? { sshOptions } : {}), }; - const error = await mergeStoredTerminalFields(row, hostData, 1, "o", isOwner); + const error = await mergeStoredSshOptions(row, hostData, 1, "o", isOwner); return { error, row }; } @@ -47,43 +41,32 @@ describe("parseTerminalConfig", () => { }); }); -describe("mergeStoredTerminalFields", () => { - it("merges an editor save into the stored 2.8 keys instead of dropping them", async () => { +describe("mergeStoredSshOptions", () => { + it("never writes terminalConfig, which has no column", async () => { const { error, row } = await merge({ terminalConfig: { startupSnippetId: 4 }, }); expect(error).toBeNull(); - expect(JSON.parse(row.terminalConfig as string)).toEqual({ - theme: "nord", - sudoPassword: "legacy-sudo", - agentSocketPath: "/owner/agent", - startupSnippetId: 4, - }); - }); - - it("leaves terminal_config alone when the payload has none", async () => { - const { row } = await merge({}); expect(row).not.toHaveProperty("terminalConfig"); }); - it("drops the legacy sudo copy once the owner saves one on its own", async () => { - const { row } = await merge({ sudoPassword: "" }); - expect(JSON.parse(row.terminalConfig as string)).not.toHaveProperty( - "sudoPassword", + it("leaves an owner's SSH options as sent", async () => { + const { row } = await merge( + {}, + true, + JSON.stringify({ agentSocketPath: "/x" }), ); + expect(JSON.parse(row.sshOptions as string)).toEqual({ + agentSocketPath: "/x", + }); }); - it("keeps the owner's private keys through a shared editor's save", async () => { + it("keeps the owner's private SSH options through a shared editor's save", async () => { const { row } = await merge( - { terminalConfig: { startupSnippetId: 2 } }, + {}, false, - JSON.stringify({ keepaliveInterval: 9 }), + JSON.stringify({ keepaliveInterval: 9, agentSocketPath: "/editor" }), ); - expect(JSON.parse(row.terminalConfig as string)).toMatchObject({ - sudoPassword: "legacy-sudo", - agentSocketPath: "/owner/agent", - startupSnippetId: 2, - }); expect(JSON.parse(row.sshOptions as string)).toEqual({ keepaliveInterval: 9, agentSocketPath: "/owner/agent", diff --git a/src/backend/tests/database/routes/user-external-account-routes.test.ts b/src/backend/tests/database/routes/user-external-account-routes.test.ts index 7c62e979b..2f89c6547 100644 --- a/src/backend/tests/database/routes/user-external-account-routes.test.ts +++ b/src/backend/tests/database/routes/user-external-account-routes.test.ts @@ -13,10 +13,7 @@ interface User { id: string; username: string; isAdmin: boolean; - isOidc: boolean; passwordHash: string; - oidcIdentifier?: string | null; - clientId?: string; } const h = vi.hoisted(() => ({ @@ -30,14 +27,21 @@ const h = vi.hoisted(() => ({ revoked: [] as string[], })); +function withIdentities(user: User | undefined) { + if (!user) return null; + return { + ...user, + isExternal: h.identities.some((row) => row.userId === user.id), + }; +} + vi.mock("../../../database/repositories/factory.js", () => ({ createCurrentUserRepository: () => ({ - findById: async (id: string) => h.users.get(id) ?? null, + findById: async (id: string) => withIdentities(h.users.get(id)), findByUsername: async (username: string) => - [...h.users.values()].find((user) => user.username === username) ?? null, - update: async (id: string, changes: Partial) => { - Object.assign(h.users.get(id)!, changes); - }, + withIdentities( + [...h.users.values()].find((user) => user.username === username), + ), }), createCurrentUserAuthRepository: () => ({ listIdentitiesForUser: async (userId: string) => @@ -84,7 +88,6 @@ beforeEach(async () => { id: "admin", username: "admin", isAdmin: true, - isOidc: false, passwordHash: "x", }, ], @@ -94,9 +97,7 @@ beforeEach(async () => { id: "sso", username: "alice-sso", isAdmin: false, - isOidc: true, passwordHash: "", - oidcIdentifier: "ldap:4:alice", }, ], [ @@ -105,7 +106,6 @@ beforeEach(async () => { id: "local", username: "alice", isAdmin: false, - isOidc: false, passwordHash: "hash", }, ], @@ -149,16 +149,12 @@ function post(path: string, body: unknown, user = "admin") { } describe("linking an external account to a password account", () => { - it("moves the identities and the old identifier, then removes the external account", async () => { - const response = await post("/link-oidc-to-password", { - oidcUserId: "sso", + it("moves the identities, then removes the external account", async () => { + const response = await post("/link-external-to-password", { + externalUserId: "sso", targetUsername: "alice", }); expect(response.status).toBe(200); - expect(h.users.get("local")).toMatchObject({ - isOidc: true, - oidcIdentifier: "ldap:4:alice", - }); expect(h.identities).toEqual([ { userId: "local", providerId: "ldap:4", subject: "alice" }, ]); @@ -166,13 +162,13 @@ describe("linking an external account to a password account", () => { expect(h.deleted).toEqual(["sso"]); }); - it("takes the new path and field name", async () => { - const response = await post("/link-external-to-password", { - externalUserId: "sso", + it("no longer takes the 2.8 path and field name", async () => { + const response = await post("/link-oidc-to-password", { + oidcUserId: "sso", targetUsername: "alice", }); - expect(response.status).toBe(200); - expect(h.identities[0].userId).toBe("local"); + expect(response.status).toBe(404); + expect(h.identities[0].userId).toBe("sso"); }); it("refuses a target that already signs in externally", async () => { @@ -186,8 +182,8 @@ describe("linking an external account to a password account", () => { it("is for admins only", async () => { const response = await post( - "/link-oidc-to-password", - { oidcUserId: "sso", targetUsername: "alice" }, + "/link-external-to-password", + { externalUserId: "sso", targetUsername: "alice" }, "local", ); expect(response.status).toBe(403); @@ -195,8 +191,8 @@ describe("linking an external account to a password account", () => { }); it("refuses a target that is not a password account", async () => { - const response = await post("/link-oidc-to-password", { - oidcUserId: "sso", + const response = await post("/link-external-to-password", { + externalUserId: "sso", targetUsername: "alice-sso", }); expect(response.status).toBe(400); @@ -207,17 +203,13 @@ describe("linking an external account to a password account", () => { id: "orig", username: "bob", isAdmin: false, - isOidc: true, passwordHash: "", - oidcIdentifier: "sub-9", }); h.users.set("dup", { id: "dup", username: "bob-1", isAdmin: false, - isOidc: true, passwordHash: "", - oidcIdentifier: null, }); h.identities.push( { userId: "orig", providerId: "legacy-oidc", subject: "sub-9" }, @@ -231,7 +223,6 @@ describe("linking an external account to a password account", () => { expect(response.status).toBe(200); expect(h.deleted).toEqual(["dup"]); - expect(h.users.get("orig")!.oidcIdentifier).toBe("sub-9"); expect( h.identities .filter((row) => row.userId === "orig") @@ -244,9 +235,7 @@ describe("linking an external account to a password account", () => { id: "other", username: "carol", isAdmin: false, - isOidc: true, passwordHash: "", - oidcIdentifier: "sub-x", }); h.identities.push({ userId: "other", @@ -264,22 +253,6 @@ describe("linking an external account to a password account", () => { describe("unlinking", () => { it("drops the identities so the provider no longer signs in as the user", async () => { - await post("/link-oidc-to-password", { - oidcUserId: "sso", - targetUsername: "alice", - }); - const response = await post("/unlink-oidc-from-password", { - userId: "local", - }); - expect(response.status).toBe(200); - expect(h.users.get("local")).toMatchObject({ - isOidc: false, - oidcIdentifier: null, - }); - expect(h.identities).toEqual([]); - }); - - it("takes the new path", async () => { await post("/link-external-to-password", { externalUserId: "sso", targetUsername: "alice", @@ -291,8 +264,15 @@ describe("unlinking", () => { expect(h.identities).toEqual([]); }); - it("refuses to leave an account with no way in", async () => { + it("no longer takes the 2.8 path", async () => { const response = await post("/unlink-oidc-from-password", { + userId: "local", + }); + expect(response.status).toBe(404); + }); + + it("refuses to leave an account with no way in", async () => { + const response = await post("/unlink-external-from-password", { userId: "sso", }); expect(response.status).toBe(400); diff --git a/src/backend/tests/hosts/credential-username.test.ts b/src/backend/tests/hosts/credential-username.test.ts index 82acfa4ad..debb0b3b9 100644 --- a/src/backend/tests/hosts/credential-username.test.ts +++ b/src/backend/tests/hosts/credential-username.test.ts @@ -52,6 +52,14 @@ describe("pickResolvedPassword", () => { }); }); +function identities(rows: Array<{ id: number; subject: string }>) { + vi.doMock("../../database/repositories/factory.js", () => ({ + createCurrentUserAuthRepository: () => ({ + listIdentitiesForUser: async () => rows, + }), + })); +} + describe("expandExternalUsername", () => { beforeEach(() => { vi.resetModules(); @@ -62,29 +70,19 @@ describe("expandExternalUsername", () => { expect(await expandExternalUsername(undefined, "user-1")).toBeUndefined(); }); - it("expands the placeholder with the user's OIDC identifier", async () => { - vi.doMock("../../database/repositories/factory.js", () => ({ - createCurrentUserRepository: () => ({ - findById: async () => ({ oidcIdentifier: "jdoe" }), - }), - })); - + it("expands the placeholder with the subject of the user's first identity", async () => { + identities([ + { id: 4, subject: "later" }, + { id: 2, subject: "jdoe" }, + ]); const { expandExternalUsername: expand } = await import("../../hosts/credential-username.js"); - expect(await expand("$oidc.preferred_username", "user-1")).toBe("jdoe"); expect(await expand("$external.username", "user-1")).toBe("jdoe"); - expect( - await expand("$external.username-$oidc.preferred_username", "user-1"), - ).toBe("jdoe-jdoe"); + expect(await expand("$external.username-x", "user-1")).toBe("jdoe-x"); }); - it("leaves the placeholder as-is when the user has no OIDC identifier", async () => { - vi.doMock("../../database/repositories/factory.js", () => ({ - createCurrentUserRepository: () => ({ - findById: async () => ({ oidcIdentifier: null }), - }), - })); - + it("no longer reads the 2.8 spelling", async () => { + identities([{ id: 1, subject: "jdoe" }]); const { expandExternalUsername: expand } = await import("../../hosts/credential-username.js"); expect(await expand("$oidc.preferred_username", "user-1")).toBe( @@ -92,81 +90,25 @@ describe("expandExternalUsername", () => { ); }); + it("leaves the placeholder as-is when the user has no external sign-in", async () => { + identities([]); + const { expandExternalUsername: expand } = + await import("../../hosts/credential-username.js"); + expect(await expand("$external.username", "user-1")).toBe( + "$external.username", + ); + }); + it("returns the username unchanged when the DB lookup throws", async () => { vi.doMock("../../database/repositories/factory.js", () => ({ - createCurrentUserRepository: () => { + createCurrentUserAuthRepository: () => { throw new Error("DB unavailable"); }, })); - const { expandExternalUsername: expand } = await import("../../hosts/credential-username.js"); - expect(await expand("$oidc.preferred_username", "user-1")).toBe( - "$oidc.preferred_username", - ); - }); - - it("strips the ldap:{providerId}: prefix for genuine LDAP users", async () => { - vi.doMock("../../database/repositories/factory.js", () => ({ - createCurrentUserRepository: () => ({ - findById: async () => ({ - oidcIdentifier: "ldap:1:jdoe", - ssoProviderId: 1, - }), - }), - createCurrentUserAuthRepository: () => ({ - listIdentitiesForUser: async () => [ - { providerId: "ldap:1", subject: "jdoe" }, - ], - }), - })); - - const { expandExternalUsername: expand } = - await import("../../hosts/credential-username.js"); - expect(await expand("$oidc.preferred_username", "user-1")).toBe("jdoe"); - }); - - it("does not strip a spoofed ldap: identifier from a non-LDAP provider", async () => { - vi.doMock("../../database/repositories/factory.js", () => ({ - createCurrentUserRepository: () => ({ - findById: async () => ({ - oidcIdentifier: "ldap:1:admin", - ssoProviderId: 1, - }), - }), - createCurrentUserAuthRepository: () => ({ - listIdentitiesForUser: async () => [ - { providerId: "1", subject: "ldap:1:admin" }, - ], - }), - })); - - const { expandExternalUsername: expand } = - await import("../../hosts/credential-username.js"); - expect(await expand("$oidc.preferred_username", "user-1")).toBe( - "ldap:1:admin", - ); - }); - - it("does not strip when the embedded provider id is not the user's provider", async () => { - vi.doMock("../../database/repositories/factory.js", () => ({ - createCurrentUserRepository: () => ({ - findById: async () => ({ - oidcIdentifier: "ldap:1:admin", - ssoProviderId: 5, - }), - }), - createCurrentUserAuthRepository: () => ({ - listIdentitiesForUser: async () => [ - { providerId: "ldap:5", subject: "admin" }, - ], - }), - })); - - const { expandExternalUsername: expand } = - await import("../../hosts/credential-username.js"); - expect(await expand("$oidc.preferred_username", "user-1")).toBe( - "ldap:1:admin", + expect(await expand("$external.username", "user-1")).toBe( + "$external.username", ); }); }); diff --git a/src/backend/tests/hosts/defaults/service.test.ts b/src/backend/tests/hosts/defaults/service.test.ts index b94b8943d..457a3c6a7 100644 --- a/src/backend/tests/hosts/defaults/service.test.ts +++ b/src/backend/tests/hosts/defaults/service.test.ts @@ -1,12 +1,20 @@ import { describe, expect, it, vi } from "vitest"; import type { PluginManifest } from "@termix/plugin-sdk/manifest"; -vi.mock("../../../database/repositories/factory.js", () => ({})); +const rows = vi.hoisted(() => ({ + owned: [] as Array>, +})); + +vi.mock("../../../database/repositories/factory.js", () => ({ + createCurrentHostDefaultsRepository: () => ({ + listOwnedBy: async () => rows.owned, + }), +})); vi.mock("../../../database/routes/host-plugin-settings.js", () => ({ hostSettingsPlugins: () => [], })); -const { validateLevelChange } = +const { folderCredentialIds, validateLevelChange } = await import("../../../hosts/defaults/service.js"); const { buildCatalog } = await import("../../../hosts/defaults/catalog.js"); @@ -113,3 +121,39 @@ describe("validateLevelChange", () => { expect(change.errors["core.jumpHosts"]).toBeDefined(); }); }); + +describe("folderCredentialIds", () => { + it("reads each folder's credential from its auth default", async () => { + rows.owned = [ + { + level: "folder", + folderId: 4, + namespace: "core", + key: "auth", + value: JSON.stringify({ authType: "credential", credentialId: 7 }), + }, + { + level: "folder", + folderId: 5, + namespace: "core", + key: "auth", + value: JSON.stringify({ authType: "none" }), + }, + { + level: "user", + folderId: null, + namespace: "core", + key: "auth", + value: JSON.stringify({ authType: "credential", credentialId: 9 }), + }, + { + level: "folder", + folderId: 6, + namespace: "fx", + key: "auth", + value: JSON.stringify({ authType: "credential", credentialId: 2 }), + }, + ]; + expect([...(await folderCredentialIds("u1"))]).toEqual([[4, 7]]); + }); +}); diff --git a/src/backend/tests/hosts/host-resolver.test.ts b/src/backend/tests/hosts/host-resolver.test.ts index e1057d643..bdada76e0 100644 --- a/src/backend/tests/hosts/host-resolver.test.ts +++ b/src/backend/tests/hosts/host-resolver.test.ts @@ -7,7 +7,6 @@ const state = vi.hoisted(() => ({ overrideCredentialId: null as number | null, credentials: new Map>(), auditCalls: [] as Record[], - folderCredentialId: null as number | null, sharedSecret: null as Record | null, activeHostAccessId: null as number | null, snapshotForUserCalls: [] as Record[], @@ -20,7 +19,6 @@ vi.mock("../../database/repositories/factory.js", () => ({ findHostById: async () => (state.host ? { ...state.host } : null), findCredentialByIdForUser: async (credentialId: number, userId: string) => state.credentials.get(`${credentialId}:${userId}`) ?? null, - findFolderCredentialId: async () => state.folderCredentialId, }), createCurrentSharedHostAuthOverrideRepository: () => ({ findCredentialId: async () => state.overrideCredentialId, @@ -114,7 +112,6 @@ function baseHost(overrides: Record = {}) { sudoPassword: "owner-sudo", jumpHosts: null, tunnelConnections: null, - terminalConfig: null, socks5ProxyChain: null, quickActions: null, overrideCredentialUsername: false, @@ -129,7 +126,6 @@ beforeEach(() => { state.overrideCredentialId = null; state.credentials.clear(); state.auditCalls = []; - state.folderCredentialId = null; state.sharedSecret = null; state.activeHostAccessId = null; state.snapshotForUserCalls = []; @@ -171,35 +167,6 @@ describe("resolveHostById", () => { expect(host.sudoPassword).toBe("owner-sudo"); }); - it("falls back to the host's folder-assigned credential when none is set on the host", async () => { - state.host = baseHost({ - authType: "credential", - credentialId: null, - folder: "switches", - username: "", - password: null, - }); - state.folderCredentialId = 11; - state.credentials.set("11:owner", { - id: 11, - username: "folder-user", - authType: "password", - password: "folder-pass", - privateKey: null, - key: null, - keyPassword: null, - keyType: null, - }); - - const host = (await resolveHostById(42, "owner")) as Record< - string, - unknown - >; - expect(host.password).toBe("folder-pass"); - expect(host.username).toBe("folder-user"); - expect(host.authType).toBe("password"); - }); - it("prefers the host's own credential over its folder's credential", async () => { state.host = baseHost({ authType: "credential", @@ -208,7 +175,6 @@ describe("resolveHostById", () => { username: "", password: null, }); - state.folderCredentialId = 11; state.credentials.set("9:owner", { id: 9, username: "host-user", @@ -281,7 +247,7 @@ describe("resolveHostById", () => { state.host = baseHost({ authType: "agent", password: null, - terminalConfig: JSON.stringify({ + sshOptions: JSON.stringify({ agentSocketPath: "/run/user/1000/ssh-agent.sock", }), }); @@ -294,7 +260,7 @@ describe("resolveHostById", () => { authType: "agent", password: null, shareSshAuth: true, - terminalConfig: JSON.stringify({ + sshOptions: JSON.stringify({ agentSocketPath: "/run/user/1000/ssh-agent.sock", }), }); @@ -307,9 +273,6 @@ describe("resolveHostById", () => { expect(host.sshOptions).toEqual({ agentSocketPath: "/run/user/1000/ssh-agent.sock", }); - expect(host.terminalConfig).toEqual({ - agentSocketPath: "/run/user/1000/ssh-agent.sock", - }); }); it("uses the recipient's credential instead of the owner's authentication", async () => { @@ -436,10 +399,7 @@ describe("resolveHostById", () => { password: "stale-owner-password", key: "stale-owner-key", credentialId: null, - terminalConfig: JSON.stringify({ - theme: "termix", - sudoPassword: "owner-sudo", - }), + sudoPassword: "owner-sudo", }); const host = (await resolveHostById(42, "recipient")) as Record< string, @@ -448,37 +408,13 @@ describe("resolveHostById", () => { expect(host.password).toBeNull(); expect(host.key).toBeNull(); expect(host.credentialId).toBeNull(); - // The owner's legacy sudo password never reaches a recipient. - expect(host.terminalConfig).toEqual({ theme: "termix" }); + // The owner's sudo password never reaches a recipient. expect(host.sudoPassword).toBeNull(); }); - it("hands the owner a 2.8 sudo password and SSH options kept in terminal_config", async () => { - state.host = baseHost({ - sudoPassword: null, - sshOptions: null, - terminalConfig: JSON.stringify({ - sudoPassword: "legacy-sudo", - keepaliveInterval: 12, - theme: "nord", - }), - }); - const host = (await resolveHostById(42, "owner")) as Record< - string, - unknown - >; - expect(host.sudoPassword).toBe("legacy-sudo"); - expect(host.sshOptions).toEqual({ keepaliveInterval: 12 }); - expect(host.terminalConfig).toEqual({ - keepaliveInterval: 12, - theme: "nord", - }); - }); - - it("prefers the ssh_options column once it is filled", async () => { + it("reads the ssh_options column", async () => { state.host = baseHost({ sshOptions: JSON.stringify({ keepaliveCountMax: 3 }), - terminalConfig: JSON.stringify({ keepaliveInterval: 12 }), }); const host = (await resolveHostById(42, "owner")) as Record< string, diff --git a/src/backend/tests/plugins/ctx-auth-login.test.ts b/src/backend/tests/plugins/ctx-auth-login.test.ts index 6269cff57..89c2f023d 100644 --- a/src/backend/tests/plugins/ctx-auth-login.test.ts +++ b/src/backend/tests/plugins/ctx-auth-login.test.ts @@ -199,7 +199,7 @@ beforeEach(() => { }); describe("a plugin's external identity", () => { - it("carries the 2.8 identifier, role map and logout claims into core", async () => { + it("carries the role map and logout claims into core", async () => { setup(); const identity = await getLoginMethod("corp-sso")!.callback!( fakeRequest() as never, @@ -211,7 +211,6 @@ describe("a plugin's external identity", () => { expect(result.kind).toBe("session"); const [user] = [...h.state.users.values()]; - expect(user).toMatchObject({ oidcIdentifier: "sub-1", ssoProviderId: 3 }); expect(h.state.identities).toContainEqual( expect.objectContaining({ providerId: "3", subject: "sub-1" }), ); @@ -219,9 +218,7 @@ describe("a plugin's external identity", () => { expect(h.manager.generateJWTToken).toHaveBeenCalledWith( user.id, expect.objectContaining({ - ssoProviderId: 3, - oidcSub: "sub-1", - oidcSid: "sid-1", + externalSession: { providerId: 3, sub: "sub-1", sid: "sid-1" }, }), ); }); @@ -238,29 +235,6 @@ describe("a plugin's external identity", () => { expect(mapped).not.toHaveProperty("unlockError", "spoofed"); expect(mapped.rateLimitUsername).toBeUndefined(); }); - - it("finds a 2.8 user by the old identifier and links the identity", async () => { - h.state.users.set("u-1", { - id: "u-1", - username: "Alice", - passwordHash: "", - isAdmin: false, - isOidc: true, - oidcIdentifier: "sub-1", - }); - setup(); - const identity = await getLoginMethod("corp-sso")!.callback!( - fakeRequest() as never, - ); - await runLogin(fakeRequest() as never, identity, { - methodId: "corp-sso", - rememberMe: false, - }); - expect(h.state.users.size).toBe(1); - expect(h.state.identities).toContainEqual( - expect.objectContaining({ userId: "u-1", providerId: "3" }), - ); - }); }); describe("ctx.auth.completeRedirectLogin", () => { @@ -306,7 +280,7 @@ describe("the other ctx.auth helpers", () => { const { auth } = setup(); expect(await auth.revokeSessions({ providerId: 3, sid: "sid-1" })).toBe(1); expect(h.manager.revokeSessionsByExternalSession).toHaveBeenCalledWith({ - ssoProviderId: 3, + providerId: 3, sub: null, sid: "sid-1", }); @@ -402,8 +376,13 @@ describe("form methods and the external second factor setting", () => { username: "Bob", passwordHash: "", isAdmin: false, - isOidc: true, - oidcIdentifier: "ldap:4:bob", + }); + h.state.identities.push({ + id: 1, + userId: "u-bob", + providerId: "ldap:4", + subject: "bob", + email: null, }); h.state.factors.push({ userId: "u-bob", diff --git a/src/backend/tests/plugins/ctx-hosts.test.ts b/src/backend/tests/plugins/ctx-hosts.test.ts index 4dca60acc..7e2fd982d 100644 --- a/src/backend/tests/plugins/ctx-hosts.test.ts +++ b/src/backend/tests/plugins/ctx-hosts.test.ts @@ -474,7 +474,7 @@ describe("ctx.hosts", () => { password: "secret", key: "PRIVATE", autostartPassword: "x", - terminalConfig: '{"keepaliveInterval":5,"fontSize":14}', + sshOptions: '{"keepaliveInterval":5}', enableDocker: true, jumpHosts: "[]", }, diff --git a/src/backend/tests/plugins/security/auth.test.ts b/src/backend/tests/plugins/security/auth.test.ts index 17f6dc0b3..f21181a06 100644 --- a/src/backend/tests/plugins/security/auth.test.ts +++ b/src/backend/tests/plugins/security/auth.test.ts @@ -107,7 +107,6 @@ function addUser(overrides: Record = {}) { username: "alice", passwordHash: bcrypt.hashSync(PASSWORD, 4), isAdmin: false, - isOidc: false, ...overrides, }; h.state.users.set(user.id as string, user as never); @@ -196,7 +195,6 @@ describe("external sign-in cannot claim another account", () => { email: null, name: "Alice", legacyIdentifier: "shared-id", - ssoProviderId: 3, }; beforeEach(() => { @@ -209,7 +207,7 @@ describe("external sign-in cannot claim another account", () => { external: true, }), ); - h.state.settings.set("oidc_auto_provision", "true"); + h.state.settings.set("external_auto_provision", "true"); }); async function signIn(identity: Record = external) { @@ -223,55 +221,21 @@ describe("external sign-in cannot claim another account", () => { return h.state.identities.find((row) => row.subject === "sub-1")?.userId; } - it("never links a local password account by its old identifier", async () => { - addUser({ id: "local", oidcIdentifier: "shared-id", isOidc: false }); - await signIn(); - expect(linkedTo()).not.toBe("local"); - }); - - it("never links an account that already has an identity", async () => { - addUser({ - id: "taken", - username: "bob", - passwordHash: "", - isOidc: true, - oidcIdentifier: "shared-id", - }); + it("never links an account that has no identity for this provider", async () => { + addUser({ id: "local", username: "Alice" }); + addUser({ id: "sso", username: "bob", passwordHash: "" }); h.state.identities.push({ id: 1, - userId: "taken", + userId: "sso", providerId: "9", - subject: "bob", + subject: "sub-1", email: null, }); await signIn(); - expect(linkedTo()).not.toBe("taken"); - }); - - it("never links an account from a different provider", async () => { - addUser({ - id: "other", - username: "bob", - passwordHash: "", - isOidc: true, - oidcIdentifier: "shared-id", - ssoProviderId: 7, - }); - await signIn(); - expect(linkedTo()).not.toBe("other"); - }); - - it("still links a genuine 2.8 account on its first sign-in", async () => { - addUser({ - id: "legacy", - username: "Alice", - passwordHash: "", - isOidc: true, - oidcIdentifier: "shared-id", - ssoProviderId: 3, - }); - await signIn(); - expect(linkedTo()).toBe("legacy"); + expect(linkedTo()).not.toBe("local"); + expect( + h.state.identities.find((row) => row.providerId === "3")?.userId, + ).not.toBe("sso"); }); it("never gives a new account a local user's username", async () => { @@ -290,7 +254,6 @@ describe("external sign-in cannot claim another account", () => { id: "sso-user", username: "carol", passwordHash: "", - isOidc: true, }); h.state.identities.push({ id: 1, diff --git a/src/backend/tests/plugins/settings-manifest.test.ts b/src/backend/tests/plugins/settings-manifest.test.ts index fdc5f172a..9e35972a8 100644 --- a/src/backend/tests/plugins/settings-manifest.test.ts +++ b/src/backend/tests/plugins/settings-manifest.test.ts @@ -398,7 +398,7 @@ describe("host field options", () => { host: { enableKey: "on", enableLabelKey: "k", fields, ...extra }, }); - it("accepts enableDefault, defaultFrom, shareRead and ownerOnly", () => { + it("accepts enableDefault, shareRead and ownerOnly", () => { expect( validateManifest( host( @@ -407,7 +407,6 @@ describe("host field options", () => { key: "mode", type: "boolean", labelKey: "k", - defaultFrom: "defaultOn", shareRead: "edit", ownerOnly: true, }, @@ -418,7 +417,7 @@ describe("host field options", () => { ).toEqual([]); }); - it("still accepts an old defaultFrom, but only as a string", () => { + it("rejects the removed defaultFrom", () => { expect( validateManifest( host([ @@ -429,11 +428,6 @@ describe("host field options", () => { defaultFrom: "missing", }, ]), - ), - ).toEqual([]); - expect( - validateManifest( - host([{ key: "mode", type: "boolean", labelKey: "k", defaultFrom: 1 }]), ).join(), ).toMatch(/defaultFrom/); }); diff --git a/src/backend/tests/upgrade/acme-ssl-settings-migration.test.ts b/src/backend/tests/upgrade/acme-ssl-settings-migration.test.ts deleted file mode 100644 index 116359d1f..000000000 --- a/src/backend/tests/upgrade/acme-ssl-settings-migration.test.ts +++ /dev/null @@ -1,144 +0,0 @@ -/** - * The ACME settings migration: a 2.8 acme_ssl_settings row comes through into - * the acme-ssl plugin's admin settings, the Cloudflare token stays encrypted - * and leaves the old row, and a second run changes nothing. - */ - -import { beforeEach, describe, expect, it, vi } from "vitest"; - -const state = vi.hoisted(() => ({ - legacy: {} as Record, - pluginRows: [] as Array<{ - key: string; - value: string | null; - encrypted: boolean; - }>, - pluginInstalled: true, -})); - -vi.mock("../../database/repositories/factory.js", () => ({ - createCurrentPluginRepository: () => ({ - findById: async (id: string) => - state.pluginInstalled && id === "acme-ssl" ? { id } : null, - }), - createCurrentSettingsRepository: () => ({ - get: async (key: string) => state.legacy[key] ?? null, - set: async (key: string, value: string) => { - state.legacy[key] = value; - }, - }), - createCurrentPluginSettingsRepository: () => ({ - get: async ( - _pluginId: string, - _scope: string, - _scopeId: string | null, - key: string, - ) => state.pluginRows.find((row) => row.key === key) ?? null, - set: async ( - _pluginId: string, - _scope: string, - _scopeId: string | null, - key: string, - value: string | null, - encrypted = false, - ) => { - state.pluginRows = state.pluginRows.filter((row) => row.key !== key); - state.pluginRows.push({ key, value, encrypted }); - }, - }), -})); - -vi.mock("../../utils/system-secret-crypto.js", () => ({ - encryptSystemSecret: async (value: string) => `enc:${value}`, -})); - -vi.mock("../../utils/logger.js", () => ({ - databaseLogger: { info: vi.fn(), warn: vi.fn() }, -})); - -import { runAcmeSslSettingsMigration } from "../../upgrade/acme-ssl-settings-migration.js"; - -const value = (key: string) => { - const row = state.pluginRows.find((candidate) => candidate.key === key); - return row?.value == null ? undefined : JSON.parse(row.value); -}; - -beforeEach(() => { - state.legacy = { - acme_ssl_settings: JSON.stringify({ - enabled: true, - domain: "termix.example.com", - email: "admin@example.com", - challengeType: "dns-cloudflare", - cloudflareToken: "cf-token", - lastIssuedAt: "2026-01-01T00:00:00.000Z", - }), - }; - state.pluginRows = []; - state.pluginInstalled = true; -}); - -describe("runAcmeSslSettingsMigration", () => { - it("moves every field and encrypts the token", async () => { - const result = await runAcmeSslSettingsMigration(); - expect(result.moved.sort()).toEqual( - [ - "autoRenew", - "challengeType", - "cloudflareToken", - "domain", - "email", - ].sort(), - ); - expect(value("domain")).toBe("termix.example.com"); - expect(value("email")).toBe("admin@example.com"); - expect(value("challengeType")).toBe("dns-cloudflare"); - expect(value("autoRenew")).toBe(true); - expect(value("cloudflareToken")).toBe("enc:cf-token"); - expect( - state.pluginRows.find((row) => row.key === "cloudflareToken")?.encrypted, - ).toBe(true); - - const left = JSON.parse(state.legacy.acme_ssl_settings); - expect(left.cloudflareToken).toBeUndefined(); - expect(left.domain).toBe("termix.example.com"); - }); - - it("maps the webroot challenge and turns renewal off for manual", async () => { - state.legacy.acme_ssl_settings = JSON.stringify({ - enabled: true, - domain: "a.example", - email: "a@example.com", - challengeType: "http-webroot", - }); - await runAcmeSslSettingsMigration(); - expect(value("challengeType")).toBe("http-01"); - expect(value("autoRenew")).toBe(true); - - state.pluginRows = []; - state.legacy.acme_ssl_settings = JSON.stringify({ - enabled: true, - challengeType: "manual", - }); - await runAcmeSslSettingsMigration(); - expect(value("autoRenew")).toBe(false); - expect(value("challengeType")).toBeUndefined(); - }); - - it("changes nothing on a second run or over values already set", async () => { - await runAcmeSslSettingsMigration(); - const snapshot = JSON.stringify(state.pluginRows); - const second = await runAcmeSslSettingsMigration(); - expect(second.moved).toEqual([]); - expect(JSON.stringify(state.pluginRows)).toBe(snapshot); - }); - - it("does nothing before the plugin row exists or without a legacy row", async () => { - state.pluginInstalled = false; - expect((await runAcmeSslSettingsMigration()).moved).toEqual([]); - state.pluginInstalled = true; - state.legacy = {}; - expect((await runAcmeSslSettingsMigration()).moved).toEqual([]); - expect(state.pluginRows).toEqual([]); - }); -}); diff --git a/src/backend/tests/upgrade/ai-settings-migration.test.ts b/src/backend/tests/upgrade/ai-settings-migration.test.ts index 64136ca7d..aff881c2c 100644 --- a/src/backend/tests/upgrade/ai-settings-migration.test.ts +++ b/src/backend/tests/upgrade/ai-settings-migration.test.ts @@ -1,10 +1,8 @@ /** * The AI settings migration. * - * An upgrade must be lossless: the admin switch, the private endpoint list, - * every user's opt-in, every host's switch and every provider key have to - * reach the ai plugin, keys encrypted and gone from the table. Running it - * twice must not duplicate or clobber anything. + * Every provider key has to reach the ai plugin, encrypted and gone from the + * table. Running it twice must not duplicate or clobber anything. */ import { beforeEach, describe, expect, it, vi } from "vitest"; @@ -127,10 +125,7 @@ vi.mock("../../utils/logger.js", () => ({ databaseLogger: { info: vi.fn(), warn: vi.fn() }, })); -import { - allowlistToText, - runAiSettingsMigration, -} from "../../upgrade/ai-settings-migration.js"; +import { runAiSettingsMigration } from "../../upgrade/ai-settings-migration.js"; const value = (scope: string, scopeId: string | null, key: string) => { const row = state.pluginRows.find( @@ -167,30 +162,7 @@ beforeEach(() => { state.deks = new Set(["u1", "u2"]); }); -describe("allowlistToText", () => { - it("turns the stored JSON array into one host per line", () => { - expect(allowlistToText('[" a ", "", "b"]')).toBe("a\nb"); - expect(allowlistToText("[]")).toBe(""); - expect(allowlistToText("nope")).toBeNull(); - }); -}); - describe("runAiSettingsMigration", () => { - it("moves admin, user and host settings", async () => { - await runAiSettingsMigration(); - - expect(value("admin", null, "globallyEnabled")).toBe(true); - expect(value("admin", null, "privateEndpoints")).toBe( - "localhost\nollama.lan", - ); - expect(value("user", "u1", "enabled")).toBe(true); - expect(value("user", "u1", "allowReadOnlyCommands")).toBe(false); - // Never asked stays unset, which the plugin reads as not enabled. - expect(value("user", "u2", "enabled")).toBeUndefined(); - expect(value("host", "5", "enableAiAssistant")).toBe(true); - expect(value("host", "6", "enableAiAssistant")).toBeUndefined(); - }); - it("moves each provider key into its owner's secrets, encrypted, and clears the column", async () => { await runAiSettingsMigration(); @@ -235,18 +207,18 @@ describe("runAiSettingsMigration", () => { expect(JSON.stringify(state.pluginRows)).toBe(first); }); - it("does not overwrite a value the plugin already has", async () => { + it("does not overwrite a key the plugin already has", async () => { state.pluginRows.push({ pluginId: "ai", - scope: "admin", - scopeId: null, - key: "globallyEnabled", - value: "false", - encrypted: false, + scope: "secret", + scopeId: "u1", + key: "provider:3", + value: JSON.stringify("sysenc:kept"), + encrypted: true, }); await runAiSettingsMigration(); - expect(value("admin", null, "globallyEnabled")).toBe(false); + expect(value("secret", "u1", "provider:3")).toBe("sysenc:kept"); }); it("does nothing until the plugin row exists", async () => { diff --git a/src/backend/tests/upgrade/docker-settings-migration.test.ts b/src/backend/tests/upgrade/docker-settings-migration.test.ts deleted file mode 100644 index 4d26034bd..000000000 --- a/src/backend/tests/upgrade/docker-settings-migration.test.ts +++ /dev/null @@ -1,197 +0,0 @@ -/** - * The Docker host settings migration. - * - * An upgrade must be lossless: every host with Docker on, and every host on - * Podman, must come through into the docker plugin's host settings, whatever - * shape the engine returned the flag in. Running it twice must not duplicate - * or clobber what it moved. - */ - -import { beforeEach, describe, expect, it, vi } from "vitest"; - -const state = vi.hoisted(() => ({ - pluginRows: [] as Array<{ - pluginId: string; - scope: string; - scopeId: string | null; - key: string; - value: string | null; - }>, - hostRows: [] as Array>, - pluginInstalled: true, - columnsGone: false, -})); - -vi.mock("../../database/repositories/factory.js", () => ({ - createCurrentPluginRepository: () => ({ - findById: async (id: string) => - state.pluginInstalled && id === "docker" ? { id } : null, - }), - createCurrentPluginSettingsRepository: () => ({ - getAll: async (_pluginId: string, scope: string, scopeId: string | null) => - state.pluginRows.filter( - (row) => row.scope === scope && row.scopeId === scopeId, - ), - set: async ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, - value: string | null, - ) => { - const existing = state.pluginRows.find( - (row) => - row.scope === scope && row.scopeId === scopeId && row.key === key, - ); - if (existing) { - existing.value = value; - return; - } - state.pluginRows.push({ pluginId, scope, scopeId, key, value }); - }, - }), -})); - -vi.mock("../../utils/crypto-migration/raw-rows.js", () => ({ - selectRows: async () => { - if (state.columnsGone) throw new Error("no such column: enable_docker"); - return state.hostRows; - }, -})); - -vi.mock("../../utils/logger.js", () => ({ - databaseLogger: { info: vi.fn(), warn: vi.fn() }, -})); - -import { - dockerSettingsFromRow, - runDockerSettingsMigration, -} from "../../upgrade/docker-settings-migration.js"; - -const hostValues = (hostId: number) => - Object.fromEntries( - state.pluginRows - .filter((row) => row.scope === "host" && row.scopeId === String(hostId)) - .map((row) => [row.key, JSON.parse(row.value ?? "null")]), - ); - -beforeEach(() => { - state.pluginRows = []; - state.hostRows = []; - state.pluginInstalled = true; - state.columnsGone = false; -}); - -describe("dockerSettingsFromRow", () => { - it("reads SQLite integers, real booleans and strings", () => { - expect( - dockerSettingsFromRow({ id: 1, enable_docker: 1, docker_config: null }), - ).toEqual({ enableDocker: true }); - expect( - dockerSettingsFromRow({ - id: 1, - enable_docker: true, - docker_config: null, - }), - ).toEqual({ enableDocker: true }); - expect( - dockerSettingsFromRow({ id: 1, enable_docker: 0, docker_config: null }), - ).toEqual({}); - expect( - dockerSettingsFromRow({ - id: 1, - enable_docker: false, - docker_config: null, - }), - ).toEqual({}); - }); - - it("keeps a Podman runtime and ignores the default or broken config", () => { - expect( - dockerSettingsFromRow({ - id: 1, - enable_docker: 1, - docker_config: '{"runtime":"podman"}', - }), - ).toEqual({ enableDocker: true, containerRuntime: "podman" }); - expect( - dockerSettingsFromRow({ - id: 1, - enable_docker: 1, - docker_config: '{"runtime":"docker"}', - }), - ).toEqual({ enableDocker: true }); - expect( - dockerSettingsFromRow({ - id: 1, - enable_docker: 1, - docker_config: "{oops", - }), - ).toEqual({ enableDocker: true }); - }); -}); - -describe("runDockerSettingsMigration", () => { - it("moves every host's docker options and skips hosts with nothing set", async () => { - state.hostRows = [ - { id: 1, enable_docker: 1, docker_config: null }, - { id: 2, enable_docker: 1, docker_config: '{"runtime":"podman"}' }, - { id: 3, enable_docker: 0, docker_config: null }, - ]; - - const result = await runDockerSettingsMigration(); - - expect(result).toEqual({ hostsMoved: 2, hostsSkipped: 0 }); - expect(hostValues(1)).toEqual({ enableDocker: true }); - expect(hostValues(2)).toEqual({ - enableDocker: true, - containerRuntime: "podman", - }); - expect(hostValues(3)).toEqual({}); - }); - - it("changes nothing when run twice", async () => { - state.hostRows = [ - { id: 1, enable_docker: 1, docker_config: '{"runtime":"podman"}' }, - ]; - await runDockerSettingsMigration(); - const before = JSON.stringify(state.pluginRows); - - const second = await runDockerSettingsMigration(); - - expect(second).toEqual({ hostsMoved: 0, hostsSkipped: 1 }); - expect(JSON.stringify(state.pluginRows)).toBe(before); - }); - - it("keeps a value already saved in the plugin", async () => { - state.pluginRows.push({ - pluginId: "docker", - scope: "host", - scopeId: "1", - key: "enableDocker", - value: "false", - }); - state.hostRows = [{ id: 1, enable_docker: 1, docker_config: null }]; - - await runDockerSettingsMigration(); - - expect(hostValues(1)).toEqual({ enableDocker: false }); - }); - - it("does nothing before the plugin row exists or once the columns are gone", async () => { - state.hostRows = [{ id: 1, enable_docker: 1, docker_config: null }]; - state.pluginInstalled = false; - expect(await runDockerSettingsMigration()).toEqual({ - hostsMoved: 0, - hostsSkipped: 0, - }); - - state.pluginInstalled = true; - state.columnsGone = true; - expect(await runDockerSettingsMigration()).toEqual({ - hostsMoved: 0, - hostsSkipped: 0, - }); - expect(state.pluginRows).toEqual([]); - }); -}); diff --git a/src/backend/tests/upgrade/external-identity-migration.test.ts b/src/backend/tests/upgrade/external-identity-migration.test.ts deleted file mode 100644 index a7acbed1d..000000000 --- a/src/backend/tests/upgrade/external-identity-migration.test.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; -import { createAuthState, type AuthState } from "../auth/auth-test-helpers.js"; - -const h = vi.hoisted(() => ({ state: null as unknown as AuthState })); - -vi.mock("../../database/repositories/factory.js", async () => { - const helpers = await import("../auth/auth-test-helpers.js"); - const names = Object.keys(helpers.fakeFactory(helpers.createAuthState())); - return Object.fromEntries( - names.map((name) => [ - name, - (...args: unknown[]) => - ( - helpers.fakeFactory(h.state) as Record< - string, - (...a: unknown[]) => unknown - > - )[name](...args), - ]), - ); -}); -vi.mock("../../utils/logger.js", () => ({ - databaseLogger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -const { runExternalIdentityMigration, parseLegacyIdentifier } = - await import("../../upgrade/external-identity-migration.js"); - -function user(id: string, extra: Record) { - h.state.users.set(id, { - id, - username: id, - passwordHash: "", - isAdmin: false, - isOidc: true, - ...extra, - }); -} - -beforeEach(() => { - h.state = createAuthState(); -}); - -describe("parseLegacyIdentifier", () => { - it("splits the prefixed forms and keeps bare subjects on their provider", () => { - expect(parseLegacyIdentifier("ldap:4:bob", null)).toEqual({ - providerId: "ldap:4", - subject: "bob", - }); - expect(parseLegacyIdentifier("github:7:42", null)).toEqual({ - providerId: "7", - subject: "42", - }); - expect(parseLegacyIdentifier("ldap:4:cn=a:b", null)).toEqual({ - providerId: "ldap:4", - subject: "cn=a:b", - }); - expect(parseLegacyIdentifier("sub-123", 3)).toEqual({ - providerId: "3", - subject: "sub-123", - }); - expect(parseLegacyIdentifier("sub-123", null)).toEqual({ - providerId: "legacy-oidc", - subject: "sub-123", - }); - expect(parseLegacyIdentifier("github:null:42", null)).toEqual({ - providerId: "legacy-oidc", - subject: "42", - }); - }); -}); - -describe("runExternalIdentityMigration", () => { - it("moves every identifier without touching the old data", async () => { - user("ldap-user", { oidcIdentifier: "ldap:4:bob", ssoProviderId: 4 }); - user("gh-user", { oidcIdentifier: "github:7:42", ssoProviderId: 7 }); - user("oidc-user", { - oidcIdentifier: "sub-123", - ssoProviderId: 3, - }); - user("env-user", { oidcIdentifier: "someone@example.com" }); - user("local", { isOidc: false, passwordHash: "hash" }); - - const result = await runExternalIdentityMigration(); - - expect(result).toEqual({ identities: 4, skipped: 0 }); - expect( - h.state.identities.map(({ userId, providerId, subject }) => ({ - userId, - providerId, - subject, - })), - ).toEqual([ - { userId: "ldap-user", providerId: "ldap:4", subject: "bob" }, - { userId: "gh-user", providerId: "7", subject: "42" }, - { userId: "oidc-user", providerId: "3", subject: "sub-123" }, - { - userId: "env-user", - providerId: "legacy-oidc", - subject: "someone@example.com", - }, - ]); - // Lossless: the old column stays. - expect(h.state.users.get("ldap-user")!.oidcIdentifier).toBe("ldap:4:bob"); - }); - - it("is idempotent", async () => { - user("ldap-user", { oidcIdentifier: "ldap:4:bob" }); - await runExternalIdentityMigration(); - const second = await runExternalIdentityMigration(); - expect(second).toEqual({ identities: 0, skipped: 1 }); - expect(h.state.identities).toHaveLength(1); - }); - - it("does not recreate a legacy-oidc link that was moved to its provider", async () => { - user("oidc-user", { oidcIdentifier: "sub-1" }); - h.state.identities.push({ - id: 1, - userId: "oidc-user", - providerId: "1", - subject: "sub-1", - email: null, - } as never); - - const result = await runExternalIdentityMigration(); - - expect(result).toEqual({ identities: 0, skipped: 1 }); - expect(h.state.identities).toHaveLength(1); - }); -}); diff --git a/src/backend/tests/upgrade/file-manager-settings-migration.test.ts b/src/backend/tests/upgrade/file-manager-settings-migration.test.ts deleted file mode 100644 index 55ef9d65b..000000000 --- a/src/backend/tests/upgrade/file-manager-settings-migration.test.ts +++ /dev/null @@ -1,163 +0,0 @@ -/** - * The file-manager host-columns migration. - * - * An upgrade must be lossless: a host that had file manager disabled, a - * custom default path, or SCP legacy mode on must keep that configuration - * once the ssh_data columns are dropped. Running it twice must not duplicate - * or clobber what it moved. - */ - -import { beforeEach, describe, expect, it, vi } from "vitest"; - -interface HostRow { - id: number; - enable_file_manager: boolean; - default_path: string | null; - scp_legacy: boolean; -} - -interface SettingsRow { - pluginId: string; - scope: string; - scopeId: string | null; - key: string; - value: string | null; -} - -const hostRows: HostRow[] = []; -const settingsRows: SettingsRow[] = []; - -const pluginSettingsRepository = { - get: async ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, - ) => - settingsRows.find( - (row) => - row.pluginId === pluginId && - row.scope === scope && - row.scopeId === scopeId && - row.key === key, - ) ?? null, - set: async ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, - value: string | null, - ) => { - const existing = settingsRows.find( - (row) => - row.pluginId === pluginId && - row.scope === scope && - row.scopeId === scopeId && - row.key === key, - ); - if (existing) { - existing.value = value; - return; - } - settingsRows.push({ pluginId, scope, scopeId, key, value }); - }, -}; - -vi.mock("../../database/repositories/factory.js", () => ({ - createCurrentPluginSettingsRepository: () => pluginSettingsRepository, -})); - -vi.mock("../../database/db/index.js", () => ({ - getDb: () => ({ - all: async () => - hostRows.filter( - (row) => - row.enable_file_manager === false || - row.default_path !== null || - row.scp_legacy, - ), - }), -})); - -const { runFileManagerSettingsMigration } = - await import("../../upgrade/file-manager-settings-migration.js"); - -function storedValue(hostId: number, key: string): unknown { - const row = settingsRows.find( - (entry) => entry.scopeId === String(hostId) && entry.key === key, - ); - return row?.value === null || row?.value === undefined - ? undefined - : JSON.parse(row.value); -} - -beforeEach(() => { - hostRows.length = 0; - settingsRows.length = 0; -}); - -describe("runFileManagerSettingsMigration", () => { - it("moves a host with file manager disabled into plugin settings", async () => { - hostRows.push({ - id: 1, - enable_file_manager: false, - default_path: "/srv/app", - scp_legacy: true, - }); - - const result = await runFileManagerSettingsMigration(); - - expect(result.moved).toBe(1); - expect(storedValue(1, "enableFileManager")).toBe(false); - expect(storedValue(1, "defaultPath")).toBe("/srv/app"); - expect(storedValue(1, "scpLegacy")).toBe(true); - }); - - it("does nothing on a fresh install with no non-default hosts", async () => { - const result = await runFileManagerSettingsMigration(); - - expect(result.moved).toBe(0); - expect(settingsRows).toEqual([]); - }); - - it("is idempotent: a second run changes nothing", async () => { - hostRows.push({ - id: 2, - enable_file_manager: false, - default_path: null, - scp_legacy: false, - }); - - await runFileManagerSettingsMigration(); - const afterFirst = JSON.parse(JSON.stringify(settingsRows)); - - const second = await runFileManagerSettingsMigration(); - - expect(second.moved).toBe(0); - expect(second.skipped).toBe(1); - expect(settingsRows).toEqual(afterFirst); - }); - - it("moves each host independently", async () => { - hostRows.push( - { - id: 1, - enable_file_manager: false, - default_path: null, - scp_legacy: false, - }, - { - id: 2, - enable_file_manager: true, - default_path: "/data", - scp_legacy: false, - }, - ); - - const result = await runFileManagerSettingsMigration(); - - expect(result.moved).toBe(2); - expect(storedValue(1, "enableFileManager")).toBe(false); - expect(storedValue(2, "defaultPath")).toBe("/data"); - }); -}); diff --git a/src/backend/tests/upgrade/host-defaults-migration.test.ts b/src/backend/tests/upgrade/host-defaults-migration.test.ts deleted file mode 100644 index 6e9ad2a56..000000000 --- a/src/backend/tests/upgrade/host-defaults-migration.test.ts +++ /dev/null @@ -1,311 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; - -type PluginRow = { - pluginId: string; - scope: string; - scopeId: string | null; - key: string; - value: string | null; -}; - -const db = vi.hoisted(() => ({ - settings: {} as Record, - pluginRows: [] as PluginRow[], - defaults: [] as Array<{ - scope: Record; - namespace: string; - key: string; - value: string; - }>, - hosts: [] as Array>, - folders: [] as Array<{ id: number; userId: string; credentialId: number }>, - folderCredential: new Map(), -})); - -const manifests = vi.hoisted(() => [ - { - id: "ssh-terminal", - contributes: { - settings: { - host: { - fields: [ - { key: "fontSize", type: "number", default: 14 }, - { key: "theme", type: "string", default: "termix" }, - { key: "localEcho", type: "select", default: "auto" }, - { key: "linkClickBehavior", type: "select", default: "confirm" }, - ], - }, - }, - }, - }, - { - id: "remote-desktop", - contributes: { - settings: { - host: { - fields: [{ key: "colorDepth", type: "select", default: "inherit" }], - }, - }, - }, - }, - { id: "host-metrics", contributes: { settings: { host: { fields: [] } } } }, -]); - -vi.mock("../../utils/logger.js", () => ({ - databaseLogger: { info: vi.fn(), warn: vi.fn() }, -})); -vi.mock("../../database/routes/host-plugin-settings.js", () => ({ - hostSettingsPlugins: () => manifests, -})); -vi.mock("../../database/db/schema.js", () => ({ - sshFolders: { id: "id", userId: "userId", credentialId: "credentialId" }, -})); -vi.mock("../../database/repositories/factory.js", () => { - const scopeKey = (scope: Record) => - scope.level === "admin" - ? "admin" - : scope.level === "user" - ? `u:${scope.userId}` - : `f:${scope.folderId}`; - return { - createCurrentSettingsRepository: () => ({ - get: async (key: string) => db.settings[key] ?? null, - set: async (key: string, value: string) => { - db.settings[key] = value; - }, - }), - createCurrentPluginSettingsRepository: () => ({ - listByKey: async (pluginId: string, scope: string, key: string) => - db.pluginRows.filter( - (row) => - row.pluginId === pluginId && row.scope === scope && row.key === key, - ), - getAll: async (pluginId: string, scope: string, scopeId: string) => - db.pluginRows.filter( - (row) => - row.pluginId === pluginId && - row.scope === scope && - row.scopeId === scopeId, - ), - set: async ( - pluginId: string, - scope: string, - scopeId: string, - key: string, - value: string, - ) => { - db.pluginRows = db.pluginRows.filter( - (row) => - !( - row.pluginId === pluginId && - row.scope === scope && - row.scopeId === scopeId && - row.key === key - ), - ); - db.pluginRows.push({ pluginId, scope, scopeId, key, value }); - }, - delete: async ( - pluginId: string, - scope: string, - scopeId: string, - key: string, - ) => { - db.pluginRows = db.pluginRows.filter( - (row) => - !( - row.pluginId === pluginId && - row.scope === scope && - row.scopeId === scopeId && - row.key === key - ), - ); - }, - }), - createCurrentHostDefaultsRepository: () => ({ - listScope: async (scope: Record) => - db.defaults.filter((row) => scopeKey(row.scope) === scopeKey(scope)), - apply: async ( - scope: Record, - set: Array<{ namespace: string; key: string; value: string }>, - ) => { - for (const entry of set) db.defaults.push({ scope, ...entry }); - }, - listHosts: async () => db.hosts, - updateHosts: async ( - patches: Array<{ id: number; values: Record }>, - ) => { - for (const patch of patches) { - Object.assign( - db.hosts.find((host) => host.id === patch.id)!, - patch.values, - ); - } - }, - }), - createCurrentHostResolutionRepository: () => ({ - findFolderCredentialId: async (_userId: string, folder: string) => - db.folderCredential.get(folder) ?? null, - }), - createCurrentRepositoryContext: () => ({ - drizzle: { - select: () => ({ - from: () => ({ where: async () => db.folders }), - }), - }, - }), - }; -}); - -const { runHostDefaultsMigration } = - await import("../../upgrade/host-defaults-migration.js"); - -function level(namespace: string, key: string, scopeLevel: string) { - const row = db.defaults.find( - (entry) => - entry.namespace === namespace && - entry.key === key && - entry.scope.level === scopeLevel, - ); - return row ? JSON.parse(row.value) : undefined; -} - -function hostValue(hostId: number, pluginId: string, key: string) { - const row = db.pluginRows.find( - (entry) => - entry.pluginId === pluginId && - entry.scope === "host" && - entry.scopeId === String(hostId) && - entry.key === key, - ); - return row?.value ? JSON.parse(row.value) : undefined; -} - -const plugin = ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, - value: unknown, -): PluginRow => ({ - pluginId, - scope, - scopeId, - key, - value: JSON.stringify(value), -}); - -beforeEach(() => { - db.settings = {}; - db.pluginRows = []; - db.defaults = []; - db.hosts = []; - db.folders = []; - db.folderCredential = new Map(); -}); - -describe("runHostDefaultsMigration", () => { - it("moves the old server defaults, dropping the credential and password", async () => { - db.settings.host_defaults = JSON.stringify({ - useSocks5: true, - socks5Host: "proxy", - socks5Port: 1081, - socks5Password: "secret", - credentialId: 3, - statusCheckEnabled: false, - }); - db.pluginRows.push( - plugin("ssh-terminal", "admin", null, "newHostFontSize", 18), - plugin("host-metrics", "admin", null, "enabledForNewHosts", false), - ); - db.folders = [{ id: 4, userId: "u1", credentialId: 9 }]; - - await runHostDefaultsMigration(); - - expect(level("core", "socks5", "admin")).toMatchObject({ - useSocks5: true, - socks5Host: "proxy", - socks5Port: 1081, - }); - expect(JSON.stringify(db.defaults)).not.toContain("secret"); - expect(level("core", "statusCheckEnabled", "admin")).toBe(false); - expect(level("ssh-terminal", "fontSize", "admin")).toBe(18); - expect(level("host-metrics", "metricsEnabled", "admin")).toBe(false); - expect(level("core", "auth", "folder")).toMatchObject({ - authType: "credential", - credentialId: 9, - }); - }); - - it("moves each user's terminal and remote desktop defaults to their own level", async () => { - db.pluginRows.push( - plugin("ssh-terminal", "user", "u1", "terminalDefaults", { - fontSize: 20, - }), - plugin("ssh-terminal", "user", "u1", "localEcho", "on"), - plugin("remote-desktop", "user", "u1", "colorDepth", "16"), - plugin("remote-desktop", "user", "u1", "disableAudio", "inherit"), - ); - await runHostDefaultsMigration(); - expect(level("ssh-terminal", "fontSize", "user")).toBe(20); - expect(level("ssh-terminal", "localEcho", "user")).toBe("on"); - expect(level("remote-desktop", "colorDepth", "user")).toBe("16"); - expect(level("remote-desktop", "disableAudio", "user")).toBeUndefined(); - }); - - it("writes in the look a host really had while it followed its user", async () => { - db.hosts = [ - { id: 1, userId: "u1", defaultOverrides: null }, - { id: 2, userId: "u1", defaultOverrides: null }, - { id: 3, userId: "u1", defaultOverrides: '{"core":[]}' }, - ]; - db.pluginRows.push( - plugin("ssh-terminal", "user", "u1", "terminalDefaults", { - fontSize: 20, - }), - plugin("ssh-terminal", "user", "u1", "localEcho", "on"), - plugin("ssh-terminal", "host", "1", "fontSize", 30), - plugin("ssh-terminal", "host", "1", "localEcho", "default"), - plugin("ssh-terminal", "host", "2", "inheritAppearance", false), - plugin("ssh-terminal", "host", "2", "fontSize", 30), - plugin("ssh-terminal", "host", "2", "localEcho", "off"), - plugin("remote-desktop", "user", "u1", "colorDepth", "24"), - ); - - await runHostDefaultsMigration(); - - // Followed the user: the user's look, and their echo mode. - expect(hostValue(1, "ssh-terminal", "fontSize")).toBe(20); - expect(hostValue(1, "ssh-terminal", "localEcho")).toBe("on"); - expect(hostValue(1, "remote-desktop", "colorDepth")).toBe("24"); - // Had its own look: kept. - expect(hostValue(2, "ssh-terminal", "fontSize")).toBe(30); - expect(hostValue(2, "ssh-terminal", "localEcho")).toBe("off"); - // Saved since host defaults: left alone. - expect(hostValue(3, "ssh-terminal", "fontSize")).toBeUndefined(); - }); - - it("gives a credential host its folder's credential", async () => { - db.hosts = [ - { - id: 1, - userId: "u1", - authType: "credential", - credentialId: null, - folder: "Prod", - defaultOverrides: null, - }, - ]; - db.folderCredential.set("Prod", 12); - await runHostDefaultsMigration(); - expect(db.hosts[0].credentialId).toBe(12); - }); - - it("moves each level once", async () => { - db.settings.host_defaults = JSON.stringify({ statusCheckEnabled: false }); - await runHostDefaultsMigration(); - db.defaults = []; - await runHostDefaultsMigration(); - expect(level("core", "statusCheckEnabled", "admin")).toBeUndefined(); - }); -}); diff --git a/src/backend/tests/upgrade/host-metrics-settings-migration.test.ts b/src/backend/tests/upgrade/host-metrics-settings-migration.test.ts deleted file mode 100644 index 748530baa..000000000 --- a/src/backend/tests/upgrade/host-metrics-settings-migration.test.ts +++ /dev/null @@ -1,200 +0,0 @@ -/** - * Host Metrics' settings leaving core: the two admin keys, the new-host - * default inside host_defaults, and the metrics half of every host's - * stats_config. Nothing may be lost, and a second run must change nothing. - */ - -import Database from "better-sqlite3"; -import { drizzle } from "drizzle-orm/better-sqlite3"; -import { beforeEach, describe, expect, it, vi } from "vitest"; - -interface Row { - scope: string; - scopeId: string | null; - key: string; - value: string | null; -} - -const state = vi.hoisted(() => ({ - coreSettings: new Map(), - pluginRows: [] as Array<{ - scope: string; - scopeId: string | null; - key: string; - value: string | null; - }>, - pluginInstalled: true, - db: null as unknown, -})); - -const find = (scope: string, scopeId: string | null, key: string) => - state.pluginRows.find( - (row) => row.scope === scope && row.scopeId === scopeId && row.key === key, - ); - -vi.mock("../../database/db/index.js", () => ({ getDb: () => state.db })); -vi.mock("../../database/repositories/factory.js", () => ({ - createCurrentPluginRepository: () => ({ - findById: async (id: string) => - state.pluginInstalled && id === "host-metrics" ? { id } : null, - }), - createCurrentPluginSettingsRepository: () => ({ - get: async ( - _pluginId: string, - scope: string, - scopeId: string | null, - key: string, - ) => find(scope, scopeId, key) ?? null, - getAll: async (_pluginId: string, scope: string, scopeId: string | null) => - state.pluginRows.filter( - (row) => row.scope === scope && row.scopeId === scopeId, - ), - set: async ( - _pluginId: string, - scope: string, - scopeId: string | null, - key: string, - value: string | null, - ) => { - const existing = find(scope, scopeId, key); - if (existing) existing.value = value; - else state.pluginRows.push({ scope, scopeId, key, value }); - }, - }), - createCurrentSettingsRepository: () => ({ - get: async (key: string) => state.coreSettings.get(key) ?? null, - }), -})); -vi.mock("../../utils/logger.js", () => ({ - databaseLogger: { info: vi.fn(), warn: vi.fn() }, -})); - -const { runHostMetricsSettingsMigration, hostSettingsFromStatsConfig } = - await import("../../upgrade/host-metrics-settings-migration.js"); - -let sqlite: Database.Database; - -const value = (scope: string, scopeId: string | null, key: string) => { - const row = find(scope, scopeId, key) as Row | undefined; - return row?.value == null ? undefined : JSON.parse(row.value); -}; - -beforeEach(() => { - state.coreSettings.clear(); - state.pluginRows = []; - state.pluginInstalled = true; - sqlite = new Database(":memory:"); - sqlite.exec( - "CREATE TABLE ssh_data (id INTEGER PRIMARY KEY, stats_config TEXT)", - ); - state.db = drizzle(sqlite); -}); - -describe("hostSettingsFromStatsConfig", () => { - it("keeps only what differs from the defaults", () => { - expect( - hostSettingsFromStatsConfig( - JSON.stringify({ - statusCheckEnabled: false, - metricsEnabled: false, - metricsInterval: 45, - useGlobalMetricsInterval: false, - enabledWidgets: ["cpu", "disk"], - excludedMounts: ["/snap"], - monitoredMounts: [{ path: "/data", label: "Data" }, { bad: 1 }], - }), - ), - ).toEqual({ - metricsEnabled: false, - metricsInterval: 45, - enabledWidgets: ["cpu", "disk"], - excludedMounts: ["/snap"], - monitoredMounts: [{ path: "/data", label: "Data" }], - }); - }); - - it("ignores the host's interval while it followed the global one", () => { - expect( - hostSettingsFromStatsConfig(JSON.stringify({ metricsInterval: 45 })), - ).toEqual({}); - }); - - it("reads a value that was stringified twice", () => { - expect( - hostSettingsFromStatsConfig( - JSON.stringify(JSON.stringify({ metricsEnabled: false })), - ), - ).toEqual({ metricsEnabled: false }); - }); -}); - -describe("runHostMetricsSettingsMigration", () => { - it("moves the admin keys, the new-host default and every host", async () => { - state.coreSettings.set("global_metrics_interval", "20"); - state.coreSettings.set("metrics_history_retention_days", "30"); - state.coreSettings.set( - "host_defaults", - JSON.stringify({ metricsEnabled: false, statusCheckEnabled: true }), - ); - const insert = sqlite.prepare( - "INSERT INTO ssh_data (id, stats_config) VALUES (?, ?)", - ); - insert.run(1, JSON.stringify({ metricsEnabled: false })); - insert.run(2, JSON.stringify({ enabledWidgets: ["cpu"] })); - insert.run(3, JSON.stringify({ statusCheckEnabled: false })); - insert.run(4, null); - - const result = await runHostMetricsSettingsMigration(); - - expect(result.moved.sort()).toEqual([ - "global_metrics_interval", - "host_defaults.metricsEnabled", - "metrics_history_retention_days", - ]); - expect(value("admin", null, "metricsInterval")).toBe(20); - expect(value("admin", null, "historyRetentionDays")).toBe(30); - expect(value("admin", null, "enabledForNewHosts")).toBe(false); - expect(value("host", "1", "metricsEnabled")).toBe(false); - expect(value("host", "2", "enabledWidgets")).toEqual(["cpu"]); - // Only the status half, which is core's: nothing for the plugin. - expect(state.pluginRows.filter((row) => row.scopeId === "3")).toEqual([]); - expect(result.hostsMoved).toBe(2); - }); - - it("changes nothing the second time", async () => { - state.coreSettings.set("global_metrics_interval", "20"); - sqlite - .prepare("INSERT INTO ssh_data (id, stats_config) VALUES (?, ?)") - .run(1, JSON.stringify({ metricsEnabled: false })); - await runHostMetricsSettingsMigration(); - - // Changed through the plugin after the upgrade. - const interval = find("admin", null, "metricsInterval")!; - interval.value = "90"; - const enabled = find("host", "1", "metricsEnabled")!; - enabled.value = "true"; - - const second = await runHostMetricsSettingsMigration(); - expect(second).toEqual({ moved: [], hostsMoved: 0, hostsSkipped: 1 }); - expect(value("admin", null, "metricsInterval")).toBe(90); - expect(value("host", "1", "metricsEnabled")).toBe(true); - }); - - it("waits until the plugin row exists", async () => { - state.pluginInstalled = false; - state.coreSettings.set("global_metrics_interval", "20"); - expect(await runHostMetricsSettingsMigration()).toEqual({ - moved: [], - hostsMoved: 0, - hostsSkipped: 0, - }); - expect(state.pluginRows).toEqual([]); - }); - - it("skips values out of range", async () => { - state.coreSettings.set("global_metrics_interval", "1"); - state.coreSettings.set("metrics_history_retention_days", "900"); - await runHostMetricsSettingsMigration(); - expect(state.pluginRows).toEqual([]); - }); -}); diff --git a/src/backend/tests/upgrade/opkssh-config-migration.test.ts b/src/backend/tests/upgrade/opkssh-config-migration.test.ts index 78ca5fb67..d54e91e7c 100644 --- a/src/backend/tests/upgrade/opkssh-config-migration.test.ts +++ b/src/backend/tests/upgrade/opkssh-config-migration.test.ts @@ -1,8 +1,7 @@ /** * The OPKSSH config move. A 2.8 install's config must reach the opkssh - * plugin's folder, the old file must stay for a downgrade, and the install - * must keep the redirect URI its identity providers know. Running it twice - * must not overwrite anything. + * plugin's folder and the old file must stay for a downgrade. Running it + * twice must not overwrite anything. */ import fs from "node:fs/promises"; @@ -56,33 +55,22 @@ afterEach(async () => { }); describe("runOpksshConfigMigration", () => { - it("copies the config, keeps the old one and the old redirect URI, once", async () => { + it("copies the config and keeps the old one, once", async () => { await fs.mkdir(path.dirname(legacy()), { recursive: true }); await fs.writeFile(legacy(), "providers:\n - alias: google\n"); - expect(await runOpksshConfigMigration(dataDir)).toEqual({ - copied: true, - legacyCallback: true, - }); + expect(await runOpksshConfigMigration(dataDir)).toEqual({ copied: true }); expect(await fs.readFile(target(), "utf8")).toContain("alias: google"); expect(await fs.readFile(legacy(), "utf8")).toContain("alias: google"); - expect(state.settings.get("legacyCallback")).toBe("true"); + expect(state.settings.has("legacyCallback")).toBe(false); await fs.writeFile(target(), "providers:\n - alias: edited\n"); - state.settings.set("legacyCallback", "false"); - expect(await runOpksshConfigMigration(dataDir)).toEqual({ - copied: false, - legacyCallback: false, - }); + expect(await runOpksshConfigMigration(dataDir)).toEqual({ copied: false }); expect(await fs.readFile(target(), "utf8")).toContain("alias: edited"); - expect(state.settings.get("legacyCallback")).toBe("false"); }); it("does nothing on an install that never set up OPKSSH", async () => { - expect(await runOpksshConfigMigration(dataDir)).toEqual({ - copied: false, - legacyCallback: false, - }); + expect(await runOpksshConfigMigration(dataDir)).toEqual({ copied: false }); expect(state.settings.size).toBe(0); }); diff --git a/src/backend/tests/upgrade/proxmox-settings-migration.test.ts b/src/backend/tests/upgrade/proxmox-settings-migration.test.ts deleted file mode 100644 index e7873ee04..000000000 --- a/src/backend/tests/upgrade/proxmox-settings-migration.test.ts +++ /dev/null @@ -1,171 +0,0 @@ -/** - * The Proxmox host-columns migration. - * - * An upgrade must be lossless: a host that already had Proxmox discovery or - * stats configured must keep that configuration once the ssh_data columns - * are dropped. Running it twice must not duplicate or clobber what it moved. - */ - -import { beforeEach, describe, expect, it, vi } from "vitest"; - -interface HostRow { - id: number; - enable_proxmox: boolean; - proxmox_config: string | null; - enable_proxmox_stats: boolean; - proxmox_stats_config: string | null; -} - -interface SettingsRow { - pluginId: string; - scope: string; - scopeId: string | null; - key: string; - value: string | null; -} - -const hostRows: HostRow[] = []; -const settingsRows: SettingsRow[] = []; - -const pluginSettingsRepository = { - get: async ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, - ) => - settingsRows.find( - (row) => - row.pluginId === pluginId && - row.scope === scope && - row.scopeId === scopeId && - row.key === key, - ) ?? null, - set: async ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, - value: string | null, - ) => { - const existing = settingsRows.find( - (row) => - row.pluginId === pluginId && - row.scope === scope && - row.scopeId === scopeId && - row.key === key, - ); - if (existing) { - existing.value = value; - return; - } - settingsRows.push({ pluginId, scope, scopeId, key, value }); - }, -}; - -vi.mock("../../database/repositories/factory.js", () => ({ - createCurrentPluginSettingsRepository: () => pluginSettingsRepository, -})); - -vi.mock("../../database/db/index.js", () => ({ - getDb: () => ({ - all: async () => - hostRows.filter( - (row) => - row.enable_proxmox || - row.proxmox_config !== null || - row.enable_proxmox_stats || - row.proxmox_stats_config !== null, - ), - }), -})); - -const { runProxmoxSettingsMigration } = - await import("../../upgrade/proxmox-settings-migration.js"); - -function storedValue(hostId: number, key: string): unknown { - const row = settingsRows.find( - (entry) => entry.scopeId === String(hostId) && entry.key === key, - ); - return row?.value === null || row?.value === undefined - ? undefined - : JSON.parse(row.value); -} - -beforeEach(() => { - hostRows.length = 0; - settingsRows.length = 0; -}); - -describe("runProxmoxSettingsMigration", () => { - it("moves an enabled host's columns into plugin settings", async () => { - hostRows.push({ - id: 1, - enable_proxmox: true, - proxmox_config: JSON.stringify({ defaultAuthType: "password" }), - enable_proxmox_stats: true, - proxmox_stats_config: JSON.stringify({ nodeName: "pve1" }), - }); - - const result = await runProxmoxSettingsMigration(); - - expect(result.moved).toBe(1); - expect(storedValue(1, "enableProxmox")).toBe(true); - expect(storedValue(1, "proxmoxConfig")).toEqual({ - defaultAuthType: "password", - }); - expect(storedValue(1, "enableProxmoxStats")).toBe(true); - expect(storedValue(1, "proxmoxStatsConfig")).toEqual({ nodeName: "pve1" }); - }); - - it("does nothing on a fresh install with no proxmox hosts", async () => { - const result = await runProxmoxSettingsMigration(); - - expect(result.moved).toBe(0); - expect(settingsRows).toEqual([]); - }); - - it("is idempotent: a second run changes nothing", async () => { - hostRows.push({ - id: 2, - enable_proxmox: false, - proxmox_config: JSON.stringify({ autoSyncEnabled: true }), - enable_proxmox_stats: false, - proxmox_stats_config: null, - }); - - await runProxmoxSettingsMigration(); - const afterFirst = JSON.parse(JSON.stringify(settingsRows)); - - const second = await runProxmoxSettingsMigration(); - - expect(second.moved).toBe(0); - expect(second.skipped).toBe(1); - expect(settingsRows).toEqual(afterFirst); - }); - - it("moves each host independently", async () => { - hostRows.push( - { - id: 1, - enable_proxmox: true, - proxmox_config: null, - enable_proxmox_stats: false, - proxmox_stats_config: null, - }, - { - id: 2, - enable_proxmox: false, - proxmox_config: null, - enable_proxmox_stats: true, - proxmox_stats_config: JSON.stringify({ nodeName: "pve2" }), - }, - ); - - const result = await runProxmoxSettingsMigration(); - - expect(result.moved).toBe(2); - expect(storedValue(1, "enableProxmox")).toBe(true); - expect(storedValue(2, "proxmoxStatsConfig")).toEqual({ nodeName: "pve2" }); - }); -}); diff --git a/src/backend/tests/upgrade/remote-desktop-settings-migration.test.ts b/src/backend/tests/upgrade/remote-desktop-settings-migration.test.ts deleted file mode 100644 index c6a1fcc00..000000000 --- a/src/backend/tests/upgrade/remote-desktop-settings-migration.test.ts +++ /dev/null @@ -1,256 +0,0 @@ -/** - * The Remote Desktop settings migration. - * - * An upgrade must be lossless: the admin switch and guacd address, each - * user's RDP defaults and every host's RDP/VNC/Telnet options must come - * through into the plugin's own settings, whatever shape an old host had. - * Running it twice must not duplicate or clobber what it moved. - */ - -import { beforeEach, describe, expect, it, vi } from "vitest"; - -interface Row { - pluginId: string; - scope: string; - scopeId: string | null; - key: string; - value: string | null; -} - -const state = vi.hoisted(() => ({ - coreSettings: new Map(), - pluginRows: [] as Array<{ - pluginId: string; - scope: string; - scopeId: string | null; - key: string; - value: string | null; - }>, - hostRows: [] as Array>, - userRows: [] as Array<{ user_id: string; rdp_defaults: string | null }>, - statements: [] as string[], - pluginInstalled: true, -})); - -const find = (scope: string, scopeId: string | null, key: string) => - state.pluginRows.find( - (row) => row.scope === scope && row.scopeId === scopeId && row.key === key, - ); - -vi.mock("../../database/repositories/factory.js", () => ({ - createCurrentPluginRepository: () => ({ - findById: async (id: string) => - state.pluginInstalled && id === "remote-desktop" ? { id } : null, - }), - createCurrentPluginSettingsRepository: () => ({ - get: async ( - _pluginId: string, - scope: string, - scopeId: string | null, - key: string, - ) => find(scope, scopeId, key) ?? null, - getAll: async (_pluginId: string, scope: string, scopeId: string | null) => - state.pluginRows.filter( - (row) => row.scope === scope && row.scopeId === scopeId, - ), - set: async ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, - value: string | null, - ) => { - const existing = find(scope, scopeId, key); - if (existing) { - existing.value = value; - return; - } - state.pluginRows.push({ pluginId, scope, scopeId, key, value }); - }, - }), - createCurrentSettingsRepository: () => ({ - get: async (key: string) => state.coreSettings.get(key) ?? null, - }), -})); - -vi.mock("../../utils/crypto-migration/raw-rows.js", () => ({ - selectRows: async (query: { queryChunks?: unknown[] }) => { - const text = JSON.stringify(query.queryChunks ?? query); - return text.includes("user_preferences") ? state.userRows : state.hostRows; - }, - runStatement: async (query: unknown) => { - state.statements.push(JSON.stringify(query)); - }, -})); - -vi.mock("../../utils/logger.js", () => ({ - databaseLogger: { info: vi.fn(), warn: vi.fn() }, -})); - -const { runRemoteDesktopSettingsMigration, hostSettingsFromRow } = - await import("../../upgrade/remote-desktop-settings-migration.js"); - -const value = (scope: string, scopeId: string | null, key: string) => { - const row = find(scope, scopeId, key) as Row | undefined; - return row?.value == null ? undefined : JSON.parse(row.value); -}; - -function host(overrides: Record) { - return { - id: 1, - port: 22, - connection_type: "ssh", - enable_rdp: 0, - enable_vnc: 0, - enable_telnet: 0, - rdp_port: 3389, - vnc_port: 5900, - telnet_port: 23, - rdp_security: null, - rdp_ignore_cert: 0, - security: null, - ignore_cert: 0, - guacamole_config: null, - enable_terminal_toolbar: 1, - ...overrides, - }; -} - -beforeEach(() => { - state.coreSettings.clear(); - state.pluginRows.length = 0; - state.hostRows.length = 0; - state.userRows.length = 0; - state.statements.length = 0; - state.pluginInstalled = true; -}); - -describe("runRemoteDesktopSettingsMigration", () => { - it("moves the admin switch and guacd address", async () => { - state.coreSettings.set("guac_enabled", "false"); - state.coreSettings.set("guac_url", "guacd:4822"); - - const result = await runRemoteDesktopSettingsMigration(); - - expect(result.moved).toEqual(["guac_enabled", "guac_url"]); - expect(value("admin", null, "enabled")).toBe(false); - expect(value("admin", null, "guacdUrl")).toBe("guacd:4822"); - }); - - it("moves a host's options, reading SQLite's 0 and 1 as booleans", async () => { - state.hostRows.push( - host({ - id: 7, - enable_rdp: 1, - enable_vnc: 0, - rdp_port: 3390, - rdp_security: "nla", - rdp_ignore_cert: 1, - guacamole_config: JSON.stringify({ "color-depth": 24 }), - enable_terminal_toolbar: 0, - }), - ); - - const result = await runRemoteDesktopSettingsMigration(); - - expect(result.hostsMoved).toBe(1); - expect(value("host", "7", "enableRdp")).toBe(true); - expect(value("host", "7", "enableVnc")).toBeUndefined(); - expect(value("host", "7", "rdpPort")).toBe(3390); - expect(value("host", "7", "rdpSecurity")).toBe("nla"); - expect(value("host", "7", "rdpIgnoreCert")).toBe(true); - expect(value("host", "7", "guacamoleConfig")).toEqual({ - "color-depth": 24, - }); - expect(value("host", "7", "enableToolbar")).toBe(false); - }); - - it("leaves a plain SSH host alone", async () => { - state.hostRows.push(host({ id: 3 })); - const result = await runRemoteDesktopSettingsMigration(); - expect(result.hostsMoved).toBe(0); - expect(state.pluginRows).toHaveLength(0); - }); - - it("reads real booleans too, as Postgres returns them", async () => { - state.hostRows.push(host({ id: 8, enable_vnc: true, vnc_port: 5901 })); - await runRemoteDesktopSettingsMigration(); - expect(value("host", "8", "enableVnc")).toBe(true); - expect(value("host", "8", "vncPort")).toBe(5901); - }); - - it("moves each user's RDP defaults", async () => { - state.userRows.push({ - user_id: "u1", - rdp_defaults: JSON.stringify({ - colorDepth: 24, - resizeMethod: "reconnect", - disableCopy: true, - enableWallpaper: false, - }), - }); - state.userRows.push({ user_id: "u2", rdp_defaults: null }); - - const result = await runRemoteDesktopSettingsMigration(); - - expect(result.usersMoved).toBe(1); - expect(value("user", "u1", "colorDepth")).toBe("24"); - expect(value("user", "u1", "resizeMethod")).toBe("reconnect"); - expect(value("user", "u1", "disableCopy")).toBe("on"); - expect(value("user", "u1", "enableWallpaper")).toBe("off"); - }); - - it("changes nothing when run twice", async () => { - state.coreSettings.set("guac_url", "guacd:4822"); - state.hostRows.push(host({ id: 7, enable_rdp: 1, rdp_port: 3390 })); - state.userRows.push({ - user_id: "u1", - rdp_defaults: JSON.stringify({ colorDepth: 16 }), - }); - - await runRemoteDesktopSettingsMigration(); - const firstRun = JSON.stringify(state.pluginRows); - // Changed after the first run: must not be overwritten by the old values. - find("host", "7", "rdpPort")!.value = JSON.stringify(4000); - const second = await runRemoteDesktopSettingsMigration(); - - expect(second.moved).toEqual([]); - expect(second.usersMoved).toBe(0); - expect(second.hostsMoved).toBe(0); - expect(second.hostsSkipped).toBe(1); - expect(value("host", "7", "rdpPort")).toBe(4000); - expect(JSON.parse(firstRun)).toHaveLength(state.pluginRows.length); - }); - - it("does nothing until the plugin row exists", async () => { - state.pluginInstalled = false; - state.coreSettings.set("guac_url", "guacd:4822"); - state.hostRows.push(host({ id: 7, enable_rdp: 1 })); - await runRemoteDesktopSettingsMigration(); - expect(state.pluginRows).toHaveLength(0); - }); -}); - -describe("hostSettingsFromRow", () => { - it("turns a connection_type-only host into its protocol switch and port", () => { - expect( - hostSettingsFromRow( - host({ connection_type: "vnc", port: 5905, vnc_port: null }), - ), - ).toEqual({ enableVnc: true, vncPort: 5905 }); - }); - - it("folds the older security and ignore_cert columns in", () => { - expect( - hostSettingsFromRow( - host({ - enable_rdp: 1, - rdp_security: null, - security: "tls", - rdp_ignore_cert: null, - ignore_cert: 1, - }), - ), - ).toEqual({ enableRdp: true, rdpSecurity: "tls", rdpIgnoreCert: true }); - }); -}); diff --git a/src/backend/tests/upgrade/session-recording-settings-migration.test.ts b/src/backend/tests/upgrade/session-recording-settings-migration.test.ts deleted file mode 100644 index 4c9eb277a..000000000 --- a/src/backend/tests/upgrade/session-recording-settings-migration.test.ts +++ /dev/null @@ -1,169 +0,0 @@ -/** - * The session logging host-column migration. - * - * The legacy column defaulted to true, the plugin's enable switch defaults - * to false, so every host needs an explicit row, not just the ones that - * turned it off. Running it twice must not duplicate or clobber what it - * moved. - */ - -import { beforeEach, describe, expect, it, vi } from "vitest"; - -interface HostRow { - id: number; - enable_session_logging: boolean | number | null; -} - -interface SettingsRow { - pluginId: string; - scope: string; - scopeId: string | null; - key: string; - value: string | null; -} - -const hostRows: HostRow[] = []; -const settingsRows: SettingsRow[] = []; - -const find = ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, -) => - settingsRows.find( - (row) => - row.pluginId === pluginId && - row.scope === scope && - row.scopeId === scopeId && - row.key === key, - ); - -const pluginSettingsRepository = { - get: async ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, - ) => find(pluginId, scope, scopeId, key) ?? null, - set: async ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, - value: string | null, - ) => { - const existing = find(pluginId, scope, scopeId, key); - if (existing) { - existing.value = value; - return; - } - settingsRows.push({ pluginId, scope, scopeId, key, value }); - }, -}; - -vi.mock("../../database/repositories/factory.js", () => ({ - createCurrentPluginSettingsRepository: () => pluginSettingsRepository, -})); - -vi.mock("../../database/db/index.js", () => ({ - getDb: () => ({ - all: async () => hostRows, - }), -})); - -const { runSessionRecordingSettingsMigration } = - await import("../../upgrade/session-recording-settings-migration.js"); - -function storedValue(hostId: number, key: string): unknown { - const row = settingsRows.find( - (entry) => entry.scopeId === String(hostId) && entry.key === key, - ); - return row?.value === null || row?.value === undefined - ? undefined - : JSON.parse(row.value); -} - -beforeEach(() => { - hostRows.length = 0; - settingsRows.length = 0; -}); - -describe("runSessionRecordingSettingsMigration", () => { - it("moves an enabled host's switch into plugin settings", async () => { - hostRows.push({ id: 1, enable_session_logging: true }); - - const result = await runSessionRecordingSettingsMigration(); - - expect(result.moved).toBe(1); - expect(storedValue(1, "enableSessionRecording")).toBe(true); - expect( - settingsRows.every((row) => row.pluginId === "session-recording"), - ).toBe(true); - expect(settingsRows.every((row) => row.scope === "host")).toBe(true); - }); - - it("moves a disabled host's switch too, since the defaults flipped", async () => { - hostRows.push({ id: 3, enable_session_logging: false }); - - const result = await runSessionRecordingSettingsMigration(); - - expect(result.moved).toBe(1); - expect(storedValue(3, "enableSessionRecording")).toBe(false); - }); - - it("reads SQLite 0 and 1 as off and on", async () => { - hostRows.push({ id: 6, enable_session_logging: 0 }); - hostRows.push({ id: 7, enable_session_logging: 1 }); - - await runSessionRecordingSettingsMigration(); - - expect(storedValue(6, "enableSessionRecording")).toBe(false); - expect(storedValue(7, "enableSessionRecording")).toBe(true); - }); - - it("treats a null legacy value as on, matching the old column default", async () => { - hostRows.push({ id: 4, enable_session_logging: null }); - - const result = await runSessionRecordingSettingsMigration(); - - expect(result.moved).toBe(1); - expect(storedValue(4, "enableSessionRecording")).toBe(true); - }); - - it("does nothing on a fresh install", async () => { - const result = await runSessionRecordingSettingsMigration(); - - expect(result.moved).toBe(0); - expect(settingsRows).toEqual([]); - }); - - it("is idempotent: a second run changes nothing", async () => { - hostRows.push({ id: 2, enable_session_logging: true }); - - await runSessionRecordingSettingsMigration(); - const afterFirst = JSON.parse(JSON.stringify(settingsRows)); - - const second = await runSessionRecordingSettingsMigration(); - - expect(second.moved).toBe(0); - expect(second.skipped).toBe(1); - expect(settingsRows).toEqual(afterFirst); - }); - - it("does not overwrite a value the plugin already saved", async () => { - hostRows.push({ id: 5, enable_session_logging: true }); - await pluginSettingsRepository.set( - "session-recording", - "host", - "5", - "enableSessionRecording", - "false", - ); - - const result = await runSessionRecordingSettingsMigration(); - - expect(result.skipped).toBe(1); - expect(storedValue(5, "enableSessionRecording")).toBe(false); - }); -}); diff --git a/src/backend/tests/upgrade/session-sharing-settings-migration.test.ts b/src/backend/tests/upgrade/session-sharing-settings-migration.test.ts deleted file mode 100644 index 33489cac4..000000000 --- a/src/backend/tests/upgrade/session-sharing-settings-migration.test.ts +++ /dev/null @@ -1,151 +0,0 @@ -/** - * The session sharing settings migration. - * - * An upgrade must be lossless: an instance with sharing switched off, and a - * host with sharing switched off, must stay off once the plugin reads its own - * settings. Running it twice must not duplicate or clobber what it moved. - */ - -import { beforeEach, describe, expect, it, vi } from "vitest"; - -interface SettingsRow { - pluginId: string; - scope: string; - scopeId: string | null; - key: string; - value: string | null; -} - -const blockedHostIds: number[] = []; -const coreSettings = new Map(); -const settingsRows: SettingsRow[] = []; - -const find = ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, -) => - settingsRows.find( - (row) => - row.pluginId === pluginId && - row.scope === scope && - row.scopeId === scopeId && - row.key === key, - ); - -const pluginSettingsRepository = { - get: async ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, - ) => find(pluginId, scope, scopeId, key) ?? null, - set: async ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, - value: string | null, - ) => { - const existing = find(pluginId, scope, scopeId, key); - if (existing) { - existing.value = value; - return; - } - settingsRows.push({ pluginId, scope, scopeId, key, value }); - }, -}; - -vi.mock("../../database/repositories/factory.js", () => ({ - createCurrentPluginSettingsRepository: () => pluginSettingsRepository, - createCurrentSettingsRepository: () => ({ - get: async (key: string) => coreSettings.get(key) ?? null, - }), -})); - -vi.mock("../../database/db/index.js", () => ({ - getDb: () => ({ - all: async () => blockedHostIds.map((id) => ({ id })), - }), -})); - -const { runSessionSharingSettingsMigration } = - await import("../../upgrade/session-sharing-settings-migration.js"); - -function stored(scopeId: string | null, key: string): unknown { - const row = settingsRows.find( - (entry) => entry.scopeId === scopeId && entry.key === key, - ); - return row?.value == null ? undefined : JSON.parse(row.value); -} - -beforeEach(() => { - blockedHostIds.length = 0; - coreSettings.clear(); - settingsRows.length = 0; -}); - -describe("runSessionSharingSettingsMigration", () => { - it("moves a disabled global switch into the admin setting", async () => { - coreSettings.set("session_sharing_globally_enabled", "false"); - - const result = await runSessionSharingSettingsMigration(); - - expect(result.movedGlobal).toBe(true); - expect(stored(null, "globallyEnabled")).toBe(false); - expect(settingsRows[0]).toMatchObject({ - pluginId: "session-sharing", - scope: "admin", - }); - }); - - it("moves hosts that turned sharing off into host settings", async () => { - blockedHostIds.push(4, 9); - - const result = await runSessionSharingSettingsMigration(); - - expect(result.movedHosts).toBe(2); - expect(stored("4", "allowSessionSharing")).toBe(false); - expect(stored("9", "allowSessionSharing")).toBe(false); - }); - - it("writes nothing when sharing was on everywhere", async () => { - coreSettings.set("session_sharing_globally_enabled", "true"); - - const result = await runSessionSharingSettingsMigration(); - - expect(result).toEqual({ movedGlobal: false, movedHosts: 0, skipped: 0 }); - expect(settingsRows).toEqual([]); - }); - - it("is idempotent: a second run changes nothing", async () => { - coreSettings.set("session_sharing_globally_enabled", "false"); - blockedHostIds.push(2); - - await runSessionSharingSettingsMigration(); - const afterFirst = JSON.parse(JSON.stringify(settingsRows)); - const second = await runSessionSharingSettingsMigration(); - - expect(second.movedGlobal).toBe(false); - expect(second.movedHosts).toBe(0); - expect(second.skipped).toBe(2); - expect(settingsRows).toEqual(afterFirst); - }); - - it("does not overwrite a value the plugin already saved", async () => { - blockedHostIds.push(5); - await pluginSettingsRepository.set( - "session-sharing", - "host", - "5", - "allowSessionSharing", - "true", - ); - - const result = await runSessionSharingSettingsMigration(); - - expect(result.skipped).toBe(1); - expect(stored("5", "allowSessionSharing")).toBe(true); - }); -}); diff --git a/src/backend/tests/upgrade/snippets-settings-migration.test.ts b/src/backend/tests/upgrade/snippets-settings-migration.test.ts deleted file mode 100644 index 37d4cda68..000000000 --- a/src/backend/tests/upgrade/snippets-settings-migration.test.ts +++ /dev/null @@ -1,174 +0,0 @@ -/** - * The snippets settings migration: host quick actions, the startup snippet - * and the two snippet user preferences must come through into the snippets - * plugin's settings, and running it twice must change nothing. - */ - -import { beforeEach, describe, expect, it, vi } from "vitest"; - -interface Row { - pluginId: string; - scope: string; - scopeId: string | null; - key: string; - value: string | null; -} - -const pluginRows: Row[] = []; -const hostRows: Array> = []; -const preferenceRows: Array> = []; -let pluginInstalled = true; - -const find = (scope: string, scopeId: string | null, key: string) => - pluginRows.find( - (row) => - row.pluginId === "snippets" && - row.scope === scope && - row.scopeId === scopeId && - row.key === key, - ); - -vi.mock("../../database/repositories/factory.js", () => ({ - createCurrentPluginRepository: () => ({ - findById: async (id: string) => - pluginInstalled && id === "snippets" ? { id } : null, - }), - createCurrentPluginSettingsRepository: () => ({ - get: async ( - _pluginId: string, - scope: string, - scopeId: string | null, - key: string, - ) => find(scope, scopeId, key) ?? null, - set: async ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, - value: string | null, - ) => { - const existing = find(scope, scopeId, key); - if (existing) existing.value = value; - else pluginRows.push({ pluginId, scope, scopeId, key, value }); - }, - }), -})); - -vi.mock("../../database/db/index.js", () => ({ - getDb: () => ({ - all: async (query: unknown) => - JSON.stringify(query).includes("user_preferences") - ? preferenceRows - : hostRows, - }), -})); - -const { runSnippetsSettingsMigration, snippetHostSettingsFromRow } = - await import("../../upgrade/snippets-settings-migration.js"); - -function value(scope: string, scopeId: string, key: string): unknown { - const row = find(scope, scopeId, key); - return row?.value == null ? undefined : JSON.parse(row.value); -} - -beforeEach(() => { - pluginRows.length = 0; - hostRows.length = 0; - preferenceRows.length = 0; - pluginInstalled = true; -}); - -describe("snippetHostSettingsFromRow", () => { - it("reads quick actions and the startup snippet, dropping broken rows", () => { - expect( - snippetHostSettingsFromRow({ - quick_actions: JSON.stringify([ - { name: "Restart", snippetId: 3 }, - { name: "Broken", snippetId: "x" }, - { name: "Str", snippetId: "4" }, - ]), - terminal_config: JSON.stringify({ fontSize: 14, startupSnippetId: 9 }), - }), - ).toEqual({ - quickActions: [ - { name: "Restart", snippetId: 3 }, - { name: "Str", snippetId: 4 }, - ], - startupSnippetId: 9, - }); - }); - - it("reads nothing from a row without either", () => { - expect( - snippetHostSettingsFromRow({ - quick_actions: "[]", - terminal_config: JSON.stringify({ fontSize: 14 }), - }), - ).toEqual({}); - }); -}); - -describe("runSnippetsSettingsMigration", () => { - it("moves host and user values once", async () => { - hostRows.push( - { - id: 1, - quick_actions: JSON.stringify([{ name: "Up", snippetId: 2 }]), - terminal_config: JSON.stringify({ startupSnippetId: 5 }), - }, - { id: 2, quick_actions: null, terminal_config: "{}" }, - ); - preferenceRows.push( - { user_id: "u1", confirm_snippet_execution: 1, folders_collapsed: 0 }, - { - user_id: "u2", - confirm_snippet_execution: null, - folders_collapsed: null, - }, - ); - - const first = await runSnippetsSettingsMigration(); - expect(first).toEqual({ hostsMoved: 1, hostsSkipped: 0, usersMoved: 1 }); - expect(value("host", "1", "quickActions")).toEqual([ - { name: "Up", snippetId: 2 }, - ]); - expect(value("host", "1", "startupSnippetId")).toBe(5); - expect(value("user", "u1", "confirmExecution")).toBe(true); - expect(value("user", "u1", "foldersCollapsed")).toBe(false); - expect(find("user", "u2", "confirmExecution")).toBeUndefined(); - - const snapshot = JSON.stringify(pluginRows); - const second = await runSnippetsSettingsMigration(); - expect(second).toEqual({ hostsMoved: 0, hostsSkipped: 1, usersMoved: 0 }); - expect(JSON.stringify(pluginRows)).toBe(snapshot); - }); - - it("never overwrites what the user already changed in the plugin", async () => { - pluginRows.push({ - pluginId: "snippets", - scope: "host", - scopeId: "1", - key: "startupSnippetId", - value: "7", - }); - hostRows.push({ - id: 1, - quick_actions: JSON.stringify([{ name: "Up", snippetId: 2 }]), - terminal_config: JSON.stringify({ startupSnippetId: 5 }), - }); - await runSnippetsSettingsMigration(); - expect(value("host", "1", "startupSnippetId")).toBe(7); - expect(find("host", "1", "quickActions")).toBeUndefined(); - }); - - it("does nothing while the plugin is not installed", async () => { - pluginInstalled = false; - hostRows.push({ - id: 1, - quick_actions: JSON.stringify([{ name: "Up", snippetId: 2 }]), - terminal_config: null, - }); - await runSnippetsSettingsMigration(); - expect(pluginRows).toHaveLength(0); - }); -}); diff --git a/src/backend/tests/upgrade/ssh-terminal-look-migration.test.ts b/src/backend/tests/upgrade/ssh-terminal-look-migration.test.ts deleted file mode 100644 index 3f9a470f2..000000000 --- a/src/backend/tests/upgrade/ssh-terminal-look-migration.test.ts +++ /dev/null @@ -1,248 +0,0 @@ -/** - * The terminal's look and behavior moving out of ssh_data.terminal_config and - * user_preferences into the ssh-terminal plugin's settings. Lossless, and a - * second run changes nothing. - */ - -import Database from "better-sqlite3"; -import { drizzle } from "drizzle-orm/better-sqlite3"; -import { beforeEach, describe, expect, it, vi } from "vitest"; - -interface Row { - pluginId: string; - scope: string; - scopeId: string | null; - key: string; - value: string | null; -} - -const state = vi.hoisted(() => ({ - db: null as unknown, - rows: [] as Row[], - installed: true, -})); - -const find = (scope: string, scopeId: string | null, key: string) => - state.rows.find( - (row) => - row.pluginId === "ssh-terminal" && - row.scope === scope && - row.scopeId === scopeId && - row.key === key, - ); - -vi.mock("../../database/db/index.js", () => ({ getDb: () => state.db })); -vi.mock("../../utils/logger.js", () => ({ - databaseLogger: { info: vi.fn(), warn: vi.fn() }, -})); -vi.mock("../../database/repositories/factory.js", () => ({ - createCurrentPluginRepository: () => ({ - findById: async (id: string) => - state.installed && id === "ssh-terminal" ? { id } : null, - }), - createCurrentPluginSettingsRepository: () => ({ - get: async ( - _pluginId: string, - scope: string, - scopeId: string | null, - key: string, - ) => find(scope, scopeId, key) ?? null, - getAllForScopeIds: async (scope: string, scopeIds: string[]) => - state.rows.filter( - (row) => row.scope === scope && scopeIds.includes(row.scopeId ?? ""), - ), - set: async ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, - value: string | null, - ) => { - const existing = find(scope, scopeId, key); - if (existing) existing.value = value; - else state.rows.push({ pluginId, scope, scopeId, key, value }); - }, - }), -})); - -const { - runSshTerminalLookMigration, - hostSettingsFromTerminalConfig, - userSettingsFromPreferences, -} = await import("../../upgrade/ssh-terminal-look-migration.js"); - -let sqlite: Database.Database; - -beforeEach(() => { - state.rows.length = 0; - state.installed = true; - sqlite = new Database(":memory:"); - sqlite.exec(` - CREATE TABLE ssh_data (id INTEGER PRIMARY KEY, terminal_config TEXT); - CREATE TABLE user_preferences ( - user_id TEXT PRIMARY KEY, - terminal_defaults TEXT, - custom_themes TEXT, - command_autocomplete INTEGER - ); - `); - state.db = drizzle(sqlite); -}); - -function host(id: number, key: string): unknown { - const row = find("host", String(id), key); - return row?.value == null ? undefined : JSON.parse(row.value); -} - -function user(id: string, key: string): unknown { - const row = find("user", id, key); - return row?.value == null ? undefined : JSON.parse(row.value); -} - -describe("hostSettingsFromTerminalConfig", () => { - it("keeps a host that followed the user's look following it", () => { - expect( - hostSettingsFromTerminalConfig( - JSON.stringify({ - autoTmux: true, - localEcho: "on", - keepaliveInterval: 30, - startupSnippetId: 3, - }), - ), - ).toEqual({ autoTmux: true, localEcho: "on" }); - }); - - it("opts a host with its own look out of the user's defaults", () => { - expect( - hostSettingsFromTerminalConfig({ - theme: "Termix Dark", - fontSize: "16", - cursorStyle: "wobble", - bellStyle: "visual", - }), - ).toEqual({ - inheritAppearance: false, - theme: "termix", - fontSize: 16, - bellStyle: "visual", - }); - }); - - it("reads nothing out of garbage", () => { - expect(hostSettingsFromTerminalConfig("not json")).toEqual({}); - expect(hostSettingsFromTerminalConfig(null)).toEqual({}); - }); -}); - -describe("userSettingsFromPreferences", () => { - it("takes the defaults, saved themes and autocomplete", () => { - expect( - userSettingsFromPreferences({ - terminal_defaults: JSON.stringify({ fontSize: 18, theme: "nord" }), - custom_themes: JSON.stringify([ - { id: "a", name: "Mine", colors: { background: "#000" } }, - ]), - command_autocomplete: 1, - }), - ).toEqual({ - terminalDefaults: { fontSize: 18, theme: "nord" }, - customThemes: [{ id: "a", name: "Mine", colors: { background: "#000" } }], - commandAutocomplete: true, - }); - expect(userSettingsFromPreferences({ command_autocomplete: null })).toEqual( - {}, - ); - }); -}); - -describe("runSshTerminalLookMigration", () => { - function seed() { - sqlite.prepare("INSERT INTO ssh_data VALUES (?, ?)").run( - 1, - JSON.stringify({ - theme: "dracula", - fontSize: 18, - autoTmux: true, - sudoPasswordAutoFill: true, - keepaliveInterval: 30, - }), - ); - sqlite - .prepare("INSERT INTO ssh_data VALUES (?, ?)") - .run(2, JSON.stringify({ passwordPromptAutoFill: false })); - sqlite - .prepare("INSERT INTO ssh_data VALUES (?, ?)") - .run(3, JSON.stringify({ keepaliveInterval: 10 })); - sqlite.prepare("INSERT INTO ssh_data VALUES (?, ?)").run(4, null); - sqlite - .prepare("INSERT INTO user_preferences VALUES (?, ?, ?, ?)") - .run( - "u1", - JSON.stringify({ cursorBlink: false }), - JSON.stringify([ - { id: "t", name: "T", colors: { background: "#111" } }, - ]), - 1, - ); - sqlite - .prepare("INSERT INTO user_preferences VALUES (?, ?, ?, ?)") - .run("u2", null, null, null); - } - - it("copies each host's terminal keys and each user's terminal preferences", async () => { - seed(); - const result = await runSshTerminalLookMigration(); - - expect(result).toEqual({ hostsMoved: 2, hostsSkipped: 0, usersMoved: 1 }); - expect(host(1, "inheritAppearance")).toBe(false); - expect(host(1, "theme")).toBe("dracula"); - expect(host(1, "fontSize")).toBe(18); - expect(host(1, "autoTmux")).toBe(true); - expect(host(1, "sudoPasswordAutoFill")).toBe(true); - expect(host(1, "keepaliveInterval")).toBeUndefined(); - expect(host(2, "passwordPromptAutoFill")).toBe(false); - expect(host(2, "inheritAppearance")).toBeUndefined(); - expect(host(3, "keepaliveInterval")).toBeUndefined(); - expect(user("u1", "terminalDefaults")).toEqual({ cursorBlink: false }); - expect(user("u1", "customThemes")).toHaveLength(1); - expect(user("u1", "commandAutocomplete")).toBe(true); - expect(state.rows.some((row) => row.scopeId === "u2")).toBe(false); - }); - - it("changes nothing on a second run, even after values were edited", async () => { - seed(); - await runSshTerminalLookMigration(); - find("host", "1", "fontSize")!.value = JSON.stringify(12); - find("user", "u1", "commandAutocomplete")!.value = JSON.stringify(false); - const before = JSON.stringify(state.rows); - - const second = await runSshTerminalLookMigration(); - - expect(second).toEqual({ hostsMoved: 0, hostsSkipped: 2, usersMoved: 0 }); - expect(JSON.stringify(state.rows)).toBe(before); - expect(host(1, "fontSize")).toBe(12); - }); - - it("waits for the plugin row", async () => { - seed(); - state.installed = false; - expect(await runSshTerminalLookMigration()).toEqual({ - hostsMoved: 0, - hostsSkipped: 0, - usersMoved: 0, - }); - expect(state.rows).toEqual([]); - }); - - it("copies nothing from a database without the columns", async () => { - sqlite = new Database(":memory:"); - sqlite.exec("CREATE TABLE ssh_data (id INTEGER PRIMARY KEY)"); - state.db = drizzle(sqlite); - expect(await runSshTerminalLookMigration()).toEqual({ - hostsMoved: 0, - hostsSkipped: 0, - usersMoved: 0, - }); - }); -}); diff --git a/src/backend/tests/upgrade/ssh-terminal-macros-migration.test.ts b/src/backend/tests/upgrade/ssh-terminal-macros-migration.test.ts deleted file mode 100644 index 6fe0c6f24..000000000 --- a/src/backend/tests/upgrade/ssh-terminal-macros-migration.test.ts +++ /dev/null @@ -1,104 +0,0 @@ -/** - * The terminal macros migration: a user's saved macros must come through - * into the ssh-terminal plugin's macros setting, and running it twice must - * change nothing. - */ - -import { beforeEach, describe, expect, it, vi } from "vitest"; - -interface Row { - pluginId: string; - scope: string; - scopeId: string | null; - key: string; - value: string | null; -} - -const pluginRows: Row[] = []; -const preferenceRows: Array> = []; - -const find = (scopeId: string, key: string) => - pluginRows.find( - (row) => - row.pluginId === "ssh-terminal" && - row.scope === "user" && - row.scopeId === scopeId && - row.key === key, - ); - -vi.mock("../../database/repositories/factory.js", () => ({ - createCurrentPluginRepository: () => ({ - findById: async (id: string) => (id === "ssh-terminal" ? { id } : null), - }), - createCurrentPluginSettingsRepository: () => ({ - get: async (_p: string, _s: string, scopeId: string, key: string) => - find(scopeId, key) ?? null, - set: async ( - pluginId: string, - scope: string, - scopeId: string, - key: string, - value: string | null, - ) => { - const existing = find(scopeId, key); - if (existing) existing.value = value; - else pluginRows.push({ pluginId, scope, scopeId, key, value }); - }, - }), -})); - -vi.mock("../../database/db/index.js", () => ({ - getDb: () => ({ all: async () => preferenceRows }), -})); - -const { runSshTerminalMacrosMigration, macrosFromPreferences } = - await import("../../upgrade/ssh-terminal-macros-migration.js"); - -const macro = { id: "m1", name: "Deploy", steps: [{ id: "s", type: "send" }] }; - -beforeEach(() => { - pluginRows.length = 0; - preferenceRows.length = 0; -}); - -describe("macrosFromPreferences", () => { - it("keeps valid macros and drops the rest", () => { - expect( - macrosFromPreferences(JSON.stringify([macro, { id: 1 }, "x"])), - ).toEqual([macro]); - expect(macrosFromPreferences("[]")).toBeNull(); - expect(macrosFromPreferences("{bad")).toBeNull(); - }); -}); - -describe("runSshTerminalMacrosMigration", () => { - it("moves each user's macros once", async () => { - preferenceRows.push( - { user_id: "u1", terminal_macros: JSON.stringify([macro]) }, - { user_id: "u2", terminal_macros: "[]" }, - ); - expect(await runSshTerminalMacrosMigration()).toEqual({ usersMoved: 1 }); - expect(JSON.parse(find("u1", "macros")!.value!)).toEqual([macro]); - expect(find("u2", "macros")).toBeUndefined(); - - const snapshot = JSON.stringify(pluginRows); - expect(await runSshTerminalMacrosMigration()).toEqual({ usersMoved: 0 }); - expect(JSON.stringify(pluginRows)).toBe(snapshot); - }); - - it("leaves a user who already has macros in the plugin alone", async () => { - pluginRows.push({ - pluginId: "ssh-terminal", - scope: "user", - scopeId: "u1", - key: "macros", - value: "[]", - }); - preferenceRows.push({ - user_id: "u1", - terminal_macros: JSON.stringify([macro]), - }); - await runSshTerminalMacrosMigration(); - expect(find("u1", "macros")!.value).toBe("[]"); - }); -}); diff --git a/src/backend/tests/upgrade/ssh-terminal-settings-migration.test.ts b/src/backend/tests/upgrade/ssh-terminal-settings-migration.test.ts deleted file mode 100644 index ce3e73b63..000000000 --- a/src/backend/tests/upgrade/ssh-terminal-settings-migration.test.ts +++ /dev/null @@ -1,241 +0,0 @@ -/** - * The SSH terminal settings migration. - * - * An upgrade must be lossless: an install's session timeout, command history - * switch, touch tuning and image storage settings, and every host that turned - * a terminal switch off, must come through into the plugin's own settings. - * Running it twice must not duplicate or clobber what it moved. - */ - -import { beforeEach, describe, expect, it, vi } from "vitest"; - -interface Row { - pluginId: string; - scope: string; - scopeId: string | null; - key: string; - value: string | null; - encrypted: boolean; -} - -interface HostRow { - id: number; - enable_terminal: number | null; - enable_terminal_toolbar: number | null; - enable_command_history: number | null; -} - -const coreSettings = new Map(); -const pluginRows: Row[] = []; -const hostRows: HostRow[] = []; -let pluginInstalled = true; - -const find = ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, -) => - pluginRows.find( - (row) => - row.pluginId === pluginId && - row.scope === scope && - row.scopeId === scopeId && - row.key === key, - ); - -vi.mock("../../database/repositories/factory.js", () => ({ - createCurrentPluginRepository: () => ({ - findById: async (id: string) => - pluginInstalled && id === "ssh-terminal" ? { id } : null, - }), - createCurrentPluginSettingsRepository: () => ({ - get: async ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, - ) => find(pluginId, scope, scopeId, key) ?? null, - set: async ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, - value: string | null, - encrypted = false, - ) => { - const existing = find(pluginId, scope, scopeId, key); - if (existing) { - existing.value = value; - existing.encrypted = encrypted; - return; - } - pluginRows.push({ pluginId, scope, scopeId, key, value, encrypted }); - }, - }), - createCurrentSettingsRepository: () => ({ - get: async (key: string) => coreSettings.get(key) ?? null, - }), -})); - -vi.mock("../../database/db/index.js", () => ({ - getDb: () => ({ - all: async () => - hostRows.filter( - (row) => - row.enable_terminal === 0 || - row.enable_terminal_toolbar === 0 || - row.enable_command_history === 0, - ), - }), -})); - -vi.mock("../../utils/system-secret-crypto.js", () => ({ - encryptSystemSecret: async (plaintext: string) => - plaintext.startsWith("sysenc:") ? plaintext : `sysenc:${plaintext}`, -})); - -const { runSshTerminalSettingsMigration } = - await import("../../upgrade/ssh-terminal-settings-migration.js"); - -function admin(key: string): unknown { - const row = find("ssh-terminal", "admin", null, key); - return row?.value == null ? undefined : JSON.parse(row.value); -} - -function host(id: number, key: string): unknown { - const row = find("ssh-terminal", "host", String(id), key); - return row?.value == null ? undefined : JSON.parse(row.value); -} - -beforeEach(() => { - coreSettings.clear(); - pluginRows.length = 0; - hostRows.length = 0; - pluginInstalled = true; -}); - -describe("runSshTerminalSettingsMigration", () => { - it("moves the admin settings with their types, the local dir as a secret", async () => { - coreSettings.set("terminal_session_timeout_minutes", "120"); - coreSettings.set("terminal_session_persistence_enabled", "false"); - coreSettings.set("command_history_enabled", "false"); - coreSettings.set( - "touch_input_settings", - JSON.stringify({ enabled: false, dragThresholdPx: 12 }), - ); - coreSettings.set("terminal_image_storage_mode", "local"); - coreSettings.set("terminal_image_local_dir", "/data/images"); - coreSettings.set("terminal_image_max_count", "50"); - - const result = await runSshTerminalSettingsMigration(); - - expect(result.moved).toHaveLength(7); - expect(admin("sessionTimeoutMinutes")).toBe(120); - expect(admin("sessionPersistence")).toBe(false); - expect(admin("commandHistoryEnabled")).toBe(false); - expect(admin("touchInput")).toEqual({ - enabled: false, - dragThresholdPx: 12, - }); - expect(admin("imageStorageMode")).toBe("local"); - expect(admin("imageMaxCount")).toBe(50); - const localDir = find("ssh-terminal", "admin", null, "imageLocalDir"); - expect(localDir?.encrypted).toBe(true); - expect(JSON.parse(localDir!.value!)).toBe("sysenc:/data/images"); - }); - - it("moves the terminal half of the admin host defaults to the new-host settings", async () => { - coreSettings.set( - "host_defaults", - JSON.stringify({ - useSocks5: true, - fontSize: 18, - fontFamily: "Fira Code", - theme: "Termix Dark", - cursorStyle: "wobble", - cursorBlink: false, - autoTmux: true, - }), - ); - - await runSshTerminalSettingsMigration(); - - expect(admin("newHostFontSize")).toBe(18); - expect(admin("newHostFontFamily")).toBe("Fira Code"); - expect(admin("newHostTheme")).toBe("termix"); - expect(admin("newHostCursorStyle")).toBeUndefined(); - expect(admin("newHostCursorBlink")).toBe(false); - expect(admin("newHostAutoTmux")).toBe(true); - }); - - it("copies each host that switched something off, keeping the rest on", async () => { - hostRows.push( - { - id: 1, - enable_terminal: 1, - enable_terminal_toolbar: 0, - enable_command_history: 1, - }, - { - id: 2, - enable_terminal: 0, - enable_terminal_toolbar: 1, - enable_command_history: 0, - }, - { - id: 3, - enable_terminal: 1, - enable_terminal_toolbar: 1, - enable_command_history: 1, - }, - ); - - const result = await runSshTerminalSettingsMigration(); - - expect(result.hostsMoved).toBe(2); - expect(host(1, "enableTerminal")).toBe(true); - expect(host(1, "enableTerminalToolbar")).toBe(false); - expect(host(1, "enableCommandHistory")).toBe(true); - expect(host(2, "enableTerminal")).toBe(false); - expect(host(2, "enableCommandHistory")).toBe(false); - // All on is the plugin's default, so it needs no row. - expect(host(3, "enableTerminal")).toBeUndefined(); - }); - - it("changes nothing on a second run, even after the admin changed a value", async () => { - coreSettings.set("terminal_session_timeout_minutes", "120"); - hostRows.push({ - id: 1, - enable_terminal: 0, - enable_terminal_toolbar: 1, - enable_command_history: 1, - }); - await runSshTerminalSettingsMigration(); - - const changed = find( - "ssh-terminal", - "admin", - null, - "sessionTimeoutMinutes", - ); - changed!.value = JSON.stringify(15); - const rowsBefore = pluginRows.length; - - const second = await runSshTerminalSettingsMigration(); - - expect(second.moved).toEqual([]); - expect(second.hostsMoved).toBe(0); - expect(second.hostsSkipped).toBe(1); - expect(pluginRows).toHaveLength(rowsBefore); - expect(admin("sessionTimeoutMinutes")).toBe(15); - }); - - it("does nothing until the plugin row exists", async () => { - pluginInstalled = false; - coreSettings.set("terminal_session_timeout_minutes", "120"); - const result = await runSshTerminalSettingsMigration(); - expect(result.moved).toEqual([]); - expect(pluginRows).toEqual([]); - }); -}); diff --git a/src/backend/tests/upgrade/sso-settings-migration.test.ts b/src/backend/tests/upgrade/sso-settings-migration.test.ts deleted file mode 100644 index 82518ffe8..000000000 --- a/src/backend/tests/upgrade/sso-settings-migration.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -/** - * The silent sign-in default moving from core settings into the sso plugin's - * admin settings. - */ - -import { beforeEach, describe, expect, it, vi } from "vitest"; - -const state = vi.hoisted(() => ({ - core: new Map(), - plugin: new Map(), - pluginInstalled: true, -})); - -vi.mock("../../database/repositories/factory.js", () => ({ - createCurrentPluginRepository: () => ({ - findById: async (id: string) => - state.pluginInstalled && id === "sso" ? { id } : null, - }), - createCurrentSettingsRepository: () => ({ - get: async (key: string) => state.core.get(key) ?? null, - delete: async (key: string) => { - state.core.delete(key); - }, - }), - createCurrentPluginSettingsRepository: () => ({ - get: async ( - _pluginId: string, - _scope: string, - _scopeId: string | null, - key: string, - ) => (state.plugin.has(key) ? { value: state.plugin.get(key) } : null), - set: async ( - _pluginId: string, - _scope: string, - _scopeId: string | null, - key: string, - value: string | null, - ) => { - state.plugin.set(key, value); - }, - }), -})); - -vi.mock("../../utils/logger.js", () => ({ - databaseLogger: { info: vi.fn(), warn: vi.fn() }, -})); - -import { runSsoSettingsMigration } from "../../upgrade/sso-settings-migration.js"; - -beforeEach(() => { - state.core = new Map(); - state.plugin = new Map(); - state.pluginInstalled = true; -}); - -describe("runSsoSettingsMigration", () => { - it("moves the core value into the sso admin setting", async () => { - state.core.set("oidc_silent_login_default", "true"); - expect(await runSsoSettingsMigration()).toEqual({ moved: true }); - expect(state.plugin.get("silentLoginDefault")).toBe("true"); - expect(state.core.has("oidc_silent_login_default")).toBe(false); - }); - - it("keeps a value already in plugin settings", async () => { - state.core.set("oidc_silent_login_default", "true"); - state.plugin.set("silentLoginDefault", "false"); - await runSsoSettingsMigration(); - expect(state.plugin.get("silentLoginDefault")).toBe("false"); - expect(state.core.has("oidc_silent_login_default")).toBe(false); - }); - - it("does nothing twice or without the plugin", async () => { - expect(await runSsoSettingsMigration()).toEqual({ moved: false }); - state.core.set("oidc_silent_login_default", "true"); - state.pluginInstalled = false; - expect(await runSsoSettingsMigration()).toEqual({ moved: false }); - expect(state.core.get("oidc_silent_login_default")).toBe("true"); - }); -}); diff --git a/src/backend/tests/upgrade/step-ca-settings-migration.test.ts b/src/backend/tests/upgrade/step-ca-settings-migration.test.ts deleted file mode 100644 index c41f5933a..000000000 --- a/src/backend/tests/upgrade/step-ca-settings-migration.test.ts +++ /dev/null @@ -1,116 +0,0 @@ -/** - * The Step CA settings migration: a 2.8 CA configuration and its private - * endpoint allowlist come through into the step-ca plugin's admin settings, - * the old redirect URI stays in use, and a second run changes nothing. - */ - -import { beforeEach, describe, expect, it, vi } from "vitest"; - -const state = vi.hoisted(() => ({ - legacy: {} as Record, - pluginRows: [] as Array<{ key: string; value: string | null }>, - pluginInstalled: true, -})); - -vi.mock("../../database/repositories/factory.js", () => ({ - createCurrentPluginRepository: () => ({ - findById: async (id: string) => - state.pluginInstalled && id === "step-ca" ? { id } : null, - }), - createCurrentSettingsRepository: () => ({ - get: async (key: string) => state.legacy[key] ?? null, - }), - createCurrentPluginSettingsRepository: () => ({ - get: async ( - _pluginId: string, - _scope: string, - _scopeId: string | null, - key: string, - ) => state.pluginRows.find((row) => row.key === key) ?? null, - set: async ( - _pluginId: string, - _scope: string, - _scopeId: string | null, - key: string, - value: string | null, - ) => { - state.pluginRows = state.pluginRows.filter((row) => row.key !== key); - state.pluginRows.push({ key, value }); - }, - }), -})); - -vi.mock("../../utils/logger.js", () => ({ - databaseLogger: { info: vi.fn(), warn: vi.fn() }, -})); - -import { runStepCaSettingsMigration } from "../../upgrade/step-ca-settings-migration.js"; - -const value = (key: string) => { - const row = state.pluginRows.find((candidate) => candidate.key === key); - return row?.value == null ? undefined : JSON.parse(row.value); -}; - -beforeEach(() => { - state.legacy = { - step_ca_url: "https://ca.internal:9000", - step_ca_fingerprint: "a".repeat(64), - step_ca_provisioner: "oidc", - step_ca_private_endpoint_allowlist: JSON.stringify([ - "ca.internal", - "SSO.internal", - ]), - }; - state.pluginRows = []; - state.pluginInstalled = true; -}); - -describe("runStepCaSettingsMigration", () => { - it("moves the 2.8 settings and keeps the old redirect URI", async () => { - const result = await runStepCaSettingsMigration(); - expect(result.moved).toEqual([ - "step_ca_url", - "step_ca_fingerprint", - "step_ca_provisioner", - "step_ca_private_endpoint_allowlist", - ]); - expect(result.legacyCallback).toBe(true); - expect(value("caUrl")).toBe("https://ca.internal:9000"); - expect(value("fingerprint")).toBe("a".repeat(64)); - expect(value("provisioner")).toBe("oidc"); - expect(value("privateEndpoints")).toBe("ca.internal, sso.internal"); - expect(value("legacyCallback")).toBe(true); - }); - - it("changes nothing the second time", async () => { - await runStepCaSettingsMigration(); - const before = JSON.stringify(state.pluginRows); - const again = await runStepCaSettingsMigration(); - expect(again).toEqual({ moved: [], legacyCallback: false }); - expect(JSON.stringify(state.pluginRows)).toBe(before); - }); - - it("never overwrites a value set in the plugin", async () => { - state.pluginRows = [ - { key: "caUrl", value: JSON.stringify("https://new.ca") }, - { key: "legacyCallback", value: JSON.stringify(false) }, - ]; - await runStepCaSettingsMigration(); - expect(value("caUrl")).toBe("https://new.ca"); - expect(value("legacyCallback")).toBe(false); - expect(value("provisioner")).toBe("oidc"); - }); - - it("leaves an install that never used Step CA alone", async () => { - state.legacy = {}; - const result = await runStepCaSettingsMigration(); - expect(result).toEqual({ moved: [], legacyCallback: false }); - expect(state.pluginRows).toEqual([]); - }); - - it("does nothing until the plugin row exists", async () => { - state.pluginInstalled = false; - await runStepCaSettingsMigration(); - expect(state.pluginRows).toEqual([]); - }); -}); diff --git a/src/backend/tests/upgrade/tailscale-settings-migration.test.ts b/src/backend/tests/upgrade/tailscale-settings-migration.test.ts deleted file mode 100644 index fa00c75c1..000000000 --- a/src/backend/tests/upgrade/tailscale-settings-migration.test.ts +++ /dev/null @@ -1,228 +0,0 @@ -/** - * The Tailscale settings migration. - * - * An upgrade must be lossless: an install that had a working API key must keep - * working without anyone re-entering it. That is the whole point of the test, - * and running it twice proves a restart cannot corrupt what it moved. - */ - -import { beforeEach, describe, expect, it, vi } from "vitest"; - -interface Row { - pluginId: string; - scope: string; - scopeId: string | null; - key: string; - value: string | null; - encrypted: boolean; -} - -const coreSettings = new Map(); -const pluginRows: Row[] = []; -let pluginInstalled = true; - -const pluginSettingsRepository = { - get: async ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, - ) => - pluginRows.find( - (row) => - row.pluginId === pluginId && - row.scope === scope && - row.scopeId === scopeId && - row.key === key, - ) ?? null, - set: async ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, - value: string | null, - encrypted = false, - ) => { - const existing = pluginRows.find( - (row) => - row.pluginId === pluginId && - row.scope === scope && - row.scopeId === scopeId && - row.key === key, - ); - if (existing) { - existing.value = value; - existing.encrypted = encrypted; - return; - } - pluginRows.push({ pluginId, scope, scopeId, key, value, encrypted }); - }, -}; - -vi.mock("../../database/repositories/factory.js", () => ({ - createCurrentPluginRepository: () => ({ - findById: async (id: string) => - pluginInstalled && id === "tailscale" ? { id } : null, - }), - createCurrentPluginSettingsRepository: () => pluginSettingsRepository, - createCurrentSettingsRepository: () => ({ - get: async (key: string) => coreSettings.get(key) ?? null, - delete: async (key: string) => coreSettings.delete(key), - }), -})); - -vi.mock("../../utils/system-secret-crypto.js", () => ({ - isSystemEncrypted: (value: string) => value.startsWith("sysenc:v1:"), - encryptSystemSecret: async (plaintext: string) => - !plaintext || plaintext.startsWith("sysenc:v1:") - ? plaintext - : `sysenc:v1:${Buffer.from(plaintext, "utf8").toString("base64")}`, - decryptSystemSecret: async (stored: string) => - stored.startsWith("sysenc:v1:") - ? Buffer.from(stored.slice("sysenc:v1:".length), "base64").toString( - "utf8", - ) - : stored, -})); - -const { runTailscaleSettingsMigration } = - await import("../../upgrade/tailscale-settings-migration.js"); - -function storedValue(key: string): unknown { - const row = pluginRows.find((entry) => entry.key === key); - return row?.value === null || row?.value === undefined - ? undefined - : JSON.parse(row.value); -} - -beforeEach(() => { - coreSettings.clear(); - pluginRows.length = 0; - pluginInstalled = true; -}); - -describe("runTailscaleSettingsMigration", () => { - it("moves an existing key and base URL into plugin settings", async () => { - coreSettings.set("tailscale_api_key", "tskey-api-legacy"); - coreSettings.set("tailscale_api_base_url", "https://headscale.example"); - - const result = await runTailscaleSettingsMigration(); - - expect(result.moved).toEqual([ - "tailscale_api_key", - "tailscale_api_base_url", - ]); - expect(storedValue("apiKey")).toMatch(/^sysenc:v1:/); - expect(storedValue("apiBaseUrl")).toBe("https://headscale.example"); - }); - - it("encrypts the key, which was stored in plaintext before", async () => { - coreSettings.set("tailscale_api_key", "tskey-api-legacy"); - - await runTailscaleSettingsMigration(); - - const row = pluginRows.find((entry) => entry.key === "apiKey")!; - expect(row.encrypted).toBe(true); - expect(row.value).not.toContain("tskey-api-legacy"); - }); - - it("leaves the base URL readable, since it is not a secret", async () => { - coreSettings.set("tailscale_api_base_url", "https://headscale.example"); - - await runTailscaleSettingsMigration(); - - const row = pluginRows.find((entry) => entry.key === "apiBaseUrl")!; - expect(row.encrypted).toBe(false); - }); - - it("removes the legacy rows so no plaintext key is left behind", async () => { - coreSettings.set("tailscale_api_key", "tskey-api-legacy"); - coreSettings.set("tailscale_api_base_url", "https://headscale.example"); - - await runTailscaleSettingsMigration(); - - expect(coreSettings.has("tailscale_api_key")).toBe(false); - expect(coreSettings.has("tailscale_api_base_url")).toBe(false); - }); - - it("is idempotent: a second run changes nothing", async () => { - coreSettings.set("tailscale_api_key", "tskey-api-legacy"); - coreSettings.set("tailscale_api_base_url", "https://headscale.example"); - - await runTailscaleSettingsMigration(); - const afterFirst = JSON.parse(JSON.stringify(pluginRows)); - - const second = await runTailscaleSettingsMigration(); - - expect(second.moved).toEqual([]); - expect(pluginRows).toEqual(afterFirst); - }); - - it("cannot double-encrypt the key across runs", async () => { - coreSettings.set("tailscale_api_key", "tskey-api-legacy"); - - await runTailscaleSettingsMigration(); - const first = storedValue("apiKey"); - - await runTailscaleSettingsMigration(); - - expect(storedValue("apiKey")).toBe(first); - expect(String(storedValue("apiKey")).match(/sysenc:v1:/g)).toHaveLength(1); - }); - - it("drops a stale legacy row when the value was already moved", async () => { - // An interrupted run, or a downgrade that rewrote the old key. - await pluginSettingsRepository.set( - "tailscale", - "admin", - null, - "apiKey", - JSON.stringify("sysenc:v1:already"), - true, - ); - coreSettings.set("tailscale_api_key", "tskey-plaintext-leftover"); - - const result = await runTailscaleSettingsMigration(); - - expect(result.skipped).toContain("tailscale_api_key"); - expect(storedValue("apiKey")).toBe("sysenc:v1:already"); - expect(coreSettings.has("tailscale_api_key")).toBe(false); - }); - - it("does nothing on a fresh install with no legacy rows", async () => { - const result = await runTailscaleSettingsMigration(); - - expect(result.moved).toEqual([]); - expect(pluginRows).toEqual([]); - }); - - it("skips an empty legacy value rather than storing one", async () => { - coreSettings.set("tailscale_api_key", ""); - - const result = await runTailscaleSettingsMigration(); - - expect(result.moved).toEqual([]); - expect(pluginRows).toEqual([]); - }); - - it("waits for the plugin row, because of the foreign key", async () => { - pluginInstalled = false; - coreSettings.set("tailscale_api_key", "tskey-api-legacy"); - - const result = await runTailscaleSettingsMigration(); - - expect(result.moved).toEqual([]); - expect(pluginRows).toEqual([]); - // Still there for the next boot, once the plugin is seeded. - expect(coreSettings.get("tailscale_api_key")).toBe("tskey-api-legacy"); - }); - - it("moves each key independently", async () => { - coreSettings.set("tailscale_api_key", "tskey-api-legacy"); - - const result = await runTailscaleSettingsMigration(); - - expect(result.moved).toEqual(["tailscale_api_key"]); - expect(storedValue("apiBaseUrl")).toBeUndefined(); - }); -}); diff --git a/src/backend/tests/upgrade/telemetry-settings-migration.test.ts b/src/backend/tests/upgrade/telemetry-settings-migration.test.ts deleted file mode 100644 index d9d473bd2..000000000 --- a/src/backend/tests/upgrade/telemetry-settings-migration.test.ts +++ /dev/null @@ -1,100 +0,0 @@ -/** - * The telemetry settings migration: an instance that turned telemetry off in - * 2.8 stays off, keeps its instance id, and a second run changes nothing. - */ - -import { beforeEach, describe, expect, it, vi } from "vitest"; - -interface SettingsRow { - pluginId: string; - key: string; - value: string | null; -} - -const coreSettings = new Map(); -const settingsRows: SettingsRow[] = []; -let pluginInstalled = true; - -const find = (pluginId: string, key: string) => - settingsRows.find((row) => row.pluginId === pluginId && row.key === key); - -vi.mock("../../database/repositories/factory.js", () => ({ - createCurrentPluginRepository: () => ({ - findById: async (id: string) => (pluginInstalled ? { id } : null), - }), - createCurrentPluginSettingsRepository: () => ({ - get: async (pluginId: string, _scope: string, _id: null, key: string) => - find(pluginId, key) ?? null, - set: async ( - pluginId: string, - _scope: string, - _id: null, - key: string, - value: string | null, - ) => { - const existing = find(pluginId, key); - if (existing) existing.value = value; - else settingsRows.push({ pluginId, key, value }); - }, - }), - createCurrentSettingsRepository: () => ({ - get: async (key: string) => coreSettings.get(key) ?? null, - }), -})); - -const { runTelemetrySettingsMigration } = - await import("../../upgrade/telemetry-settings-migration.js"); - -const stored = (key: string) => { - const row = find("telemetry", key); - return row?.value == null ? undefined : JSON.parse(row.value); -}; - -describe("runTelemetrySettingsMigration", () => { - beforeEach(() => { - coreSettings.clear(); - settingsRows.length = 0; - pluginInstalled = true; - }); - - it("moves a disabled switch and the instance id", async () => { - coreSettings.set("analytics_enabled", "false"); - coreSettings.set("analytics_instance_id", "abc-123"); - - const result = await runTelemetrySettingsMigration(); - - expect(result).toEqual({ movedEnabled: true, movedInstanceId: true }); - expect(stored("enabled")).toBe(false); - expect(stored("instanceId")).toBe("abc-123"); - }); - - it("writes nothing for an enabled instance without an id", async () => { - coreSettings.set("analytics_enabled", "true"); - await runTelemetrySettingsMigration(); - expect(settingsRows).toHaveLength(0); - }); - - it("is idempotent and never overwrites plugin values", async () => { - coreSettings.set("analytics_enabled", "false"); - coreSettings.set("analytics_instance_id", "old"); - settingsRows.push({ - pluginId: "telemetry", - key: "instanceId", - value: JSON.stringify("new"), - }); - - await runTelemetrySettingsMigration(); - const second = await runTelemetrySettingsMigration(); - - expect(second).toEqual({ movedEnabled: false, movedInstanceId: false }); - expect(stored("instanceId")).toBe("new"); - expect(settingsRows).toHaveLength(2); - }); - - it("does nothing without the plugin row", async () => { - pluginInstalled = false; - coreSettings.set("analytics_enabled", "false"); - await runTelemetrySettingsMigration(); - expect(settingsRows).toHaveLength(0); - }); -}); diff --git a/src/backend/tests/upgrade/tmux-monitor-settings-migration.test.ts b/src/backend/tests/upgrade/tmux-monitor-settings-migration.test.ts deleted file mode 100644 index 35031c25c..000000000 --- a/src/backend/tests/upgrade/tmux-monitor-settings-migration.test.ts +++ /dev/null @@ -1,149 +0,0 @@ -/** - * The tmux monitor host-column migration. - * - * An upgrade must be lossless: a host with the monitor on must keep it on - * once the ssh_data column is dropped. Running it twice must not duplicate - * or clobber what it moved. - */ - -import { beforeEach, describe, expect, it, vi } from "vitest"; - -interface HostRow { - id: number; - enable_tmux_monitor: boolean; -} - -interface SettingsRow { - pluginId: string; - scope: string; - scopeId: string | null; - key: string; - value: string | null; -} - -const hostRows: HostRow[] = []; -const settingsRows: SettingsRow[] = []; - -const find = ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, -) => - settingsRows.find( - (row) => - row.pluginId === pluginId && - row.scope === scope && - row.scopeId === scopeId && - row.key === key, - ); - -const pluginSettingsRepository = { - get: async ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, - ) => find(pluginId, scope, scopeId, key) ?? null, - set: async ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, - value: string | null, - ) => { - const existing = find(pluginId, scope, scopeId, key); - if (existing) { - existing.value = value; - return; - } - settingsRows.push({ pluginId, scope, scopeId, key, value }); - }, -}; - -vi.mock("../../database/repositories/factory.js", () => ({ - createCurrentPluginSettingsRepository: () => pluginSettingsRepository, -})); - -vi.mock("../../database/db/index.js", () => ({ - getDb: () => ({ - all: async () => hostRows.filter((row) => row.enable_tmux_monitor), - }), -})); - -const { runTmuxMonitorSettingsMigration } = - await import("../../upgrade/tmux-monitor-settings-migration.js"); - -function storedValue(hostId: number, key: string): unknown { - const row = settingsRows.find( - (entry) => entry.scopeId === String(hostId) && entry.key === key, - ); - return row?.value === null || row?.value === undefined - ? undefined - : JSON.parse(row.value); -} - -beforeEach(() => { - hostRows.length = 0; - settingsRows.length = 0; -}); - -describe("runTmuxMonitorSettingsMigration", () => { - it("moves a host's enabled switch into plugin settings", async () => { - hostRows.push({ id: 1, enable_tmux_monitor: true }); - - const result = await runTmuxMonitorSettingsMigration(); - - expect(result.moved).toBe(1); - expect(storedValue(1, "enableTmuxMonitor")).toBe(true); - expect(settingsRows.every((row) => row.pluginId === "tmux-monitor")).toBe( - true, - ); - expect(settingsRows.every((row) => row.scope === "host")).toBe(true); - }); - - it("leaves a disabled host untouched", async () => { - hostRows.push({ id: 3, enable_tmux_monitor: false }); - - const result = await runTmuxMonitorSettingsMigration(); - - expect(result.moved).toBe(0); - expect(storedValue(3, "enableTmuxMonitor")).toBeUndefined(); - }); - - it("does nothing on a fresh install", async () => { - const result = await runTmuxMonitorSettingsMigration(); - - expect(result.moved).toBe(0); - expect(settingsRows).toEqual([]); - }); - - it("is idempotent: a second run changes nothing", async () => { - hostRows.push({ id: 2, enable_tmux_monitor: true }); - - await runTmuxMonitorSettingsMigration(); - const afterFirst = JSON.parse(JSON.stringify(settingsRows)); - - const second = await runTmuxMonitorSettingsMigration(); - - expect(second.moved).toBe(0); - expect(second.skipped).toBe(1); - expect(settingsRows).toEqual(afterFirst); - }); - - it("does not overwrite a value the plugin already saved", async () => { - hostRows.push({ id: 5, enable_tmux_monitor: true }); - await pluginSettingsRepository.set( - "tmux-monitor", - "host", - "5", - "enableTmuxMonitor", - "false", - ); - - const result = await runTmuxMonitorSettingsMigration(); - - expect(result.skipped).toBe(1); - expect(storedValue(5, "enableTmuxMonitor")).toBe(false); - }); -}); diff --git a/src/backend/tests/upgrade/tunnels-settings-migration.test.ts b/src/backend/tests/upgrade/tunnels-settings-migration.test.ts deleted file mode 100644 index 17dc9307e..000000000 --- a/src/backend/tests/upgrade/tunnels-settings-migration.test.ts +++ /dev/null @@ -1,185 +0,0 @@ -/** - * The tunnel host-columns migration. - * - * An upgrade must be lossless: a host with tunnels on and a saved tunnel list - * must keep both once the ssh_data columns are dropped. Running it twice - * must not duplicate or clobber what it moved. - */ - -import { beforeEach, describe, expect, it, vi } from "vitest"; - -interface HostRow { - id: number; - enable_tunnel: boolean; - tunnel_connections: string | null; -} - -interface SettingsRow { - pluginId: string; - scope: string; - scopeId: string | null; - key: string; - value: string | null; -} - -const hostRows: HostRow[] = []; -const settingsRows: SettingsRow[] = []; - -const find = ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, -) => - settingsRows.find( - (row) => - row.pluginId === pluginId && - row.scope === scope && - row.scopeId === scopeId && - row.key === key, - ); - -const pluginSettingsRepository = { - get: async ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, - ) => find(pluginId, scope, scopeId, key) ?? null, - set: async ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, - value: string | null, - ) => { - const existing = find(pluginId, scope, scopeId, key); - if (existing) { - existing.value = value; - return; - } - settingsRows.push({ pluginId, scope, scopeId, key, value }); - }, -}; - -vi.mock("../../database/repositories/factory.js", () => ({ - createCurrentPluginSettingsRepository: () => pluginSettingsRepository, -})); - -vi.mock("../../database/db/index.js", () => ({ - getDb: () => ({ - all: async () => - hostRows.filter( - (row) => row.enable_tunnel || row.tunnel_connections !== null, - ), - }), -})); - -const { runTunnelsSettingsMigration } = - await import("../../upgrade/tunnels-settings-migration.js"); - -function storedValue(hostId: number, key: string): unknown { - const row = settingsRows.find( - (entry) => entry.scopeId === String(hostId) && entry.key === key, - ); - return row?.value === null || row?.value === undefined - ? undefined - : JSON.parse(row.value); -} - -const tunnel = { - scope: "s2s", - mode: "local", - sourcePort: 8080, - endpointHost: "db", - endpointPort: 5432, - maxRetries: 3, - retryInterval: 10, - autoStart: true, -}; - -beforeEach(() => { - hostRows.length = 0; - settingsRows.length = 0; -}); - -describe("runTunnelsSettingsMigration", () => { - it("moves a host's switch and saved tunnels into plugin settings", async () => { - hostRows.push({ - id: 1, - enable_tunnel: true, - tunnel_connections: JSON.stringify([tunnel]), - }); - - const result = await runTunnelsSettingsMigration(); - - expect(result.moved).toBe(1); - expect(storedValue(1, "enableTunnel")).toBe(true); - expect(storedValue(1, "tunnelConnections")).toEqual([tunnel]); - expect(settingsRows.every((row) => row.pluginId === "tunnels")).toBe(true); - expect(settingsRows.every((row) => row.scope === "host")).toBe(true); - }); - - it("keeps a disabled host's saved tunnels and its disabled switch", async () => { - hostRows.push({ - id: 3, - enable_tunnel: false, - tunnel_connections: JSON.stringify([tunnel]), - }); - - await runTunnelsSettingsMigration(); - - expect(storedValue(3, "enableTunnel")).toBe(false); - expect(storedValue(3, "tunnelConnections")).toEqual([tunnel]); - }); - - it("stores an unreadable tunnel list as empty rather than failing", async () => { - hostRows.push({ id: 4, enable_tunnel: true, tunnel_connections: "{nope" }); - - const result = await runTunnelsSettingsMigration(); - - expect(result.moved).toBe(1); - expect(storedValue(4, "tunnelConnections")).toEqual([]); - }); - - it("does nothing on a fresh install", async () => { - const result = await runTunnelsSettingsMigration(); - - expect(result.moved).toBe(0); - expect(settingsRows).toEqual([]); - }); - - it("is idempotent: a second run changes nothing", async () => { - hostRows.push({ id: 2, enable_tunnel: true, tunnel_connections: null }); - - await runTunnelsSettingsMigration(); - const afterFirst = JSON.parse(JSON.stringify(settingsRows)); - - const second = await runTunnelsSettingsMigration(); - - expect(second.moved).toBe(0); - expect(second.skipped).toBe(1); - expect(settingsRows).toEqual(afterFirst); - }); - - it("does not overwrite a value the plugin already saved", async () => { - hostRows.push({ - id: 5, - enable_tunnel: true, - tunnel_connections: JSON.stringify([tunnel]), - }); - await pluginSettingsRepository.set( - "tunnels", - "host", - "5", - "enableTunnel", - "false", - ); - - const result = await runTunnelsSettingsMigration(); - - expect(result.skipped).toBe(1); - expect(storedValue(5, "enableTunnel")).toBe(false); - expect(storedValue(5, "tunnelConnections")).toBeUndefined(); - }); -}); diff --git a/src/backend/tests/upgrade/vault-settings-migration.test.ts b/src/backend/tests/upgrade/vault-settings-migration.test.ts deleted file mode 100644 index 966ae7a5f..000000000 --- a/src/backend/tests/upgrade/vault-settings-migration.test.ts +++ /dev/null @@ -1,152 +0,0 @@ -/** - * The Vault host settings migration. - * - * Every host's ssh_data.vault_profile_id must come through into the vault - * plugin's profileId host setting, and an install that used Vault keeps the - * 2.8 redirect URI. Running it twice must change nothing. - */ - -import { beforeEach, describe, expect, it, vi } from "vitest"; - -const state = vi.hoisted(() => ({ - pluginRows: [] as Array<{ - scope: string; - scopeId: string | null; - key: string; - value: string | null; - }>, - hostRows: [] as Array>, - legacyProfiles: 0, - adoptedProfiles: 0, - pluginInstalled: true, - columnGone: false, -})); - -vi.mock("../../database/repositories/factory.js", () => ({ - createCurrentPluginRepository: () => ({ - findById: async (id: string) => - state.pluginInstalled && id === "vault" ? { id } : null, - }), - createCurrentPluginSettingsRepository: () => ({ - get: async ( - _pluginId: string, - scope: string, - scopeId: string | null, - key: string, - ) => - state.pluginRows.find( - (row) => - row.scope === scope && row.scopeId === scopeId && row.key === key, - ) ?? null, - set: async ( - _pluginId: string, - scope: string, - scopeId: string | null, - key: string, - value: string | null, - ) => { - state.pluginRows.push({ scope, scopeId, key, value }); - }, - }), -})); - -vi.mock("../../utils/crypto-migration/raw-rows.js", () => ({ - selectRows: async (query: unknown) => { - const text = JSON.stringify(query); - if (text.includes("vault_profile_id")) { - if (state.columnGone) throw new Error("no such column"); - return state.hostRows.filter((row) => row.vault_profile_id != null); - } - const count = text.includes("p_vault_profiles") - ? state.adoptedProfiles - : state.legacyProfiles; - return Array.from({ length: Math.min(count, 1) }, () => ({ "1": 1 })); - }, - runStatement: async () => {}, -})); - -vi.mock("../../utils/logger.js", () => ({ - databaseLogger: { info: vi.fn(), warn: vi.fn() }, -})); - -import { runVaultSettingsMigration } from "../../upgrade/vault-settings-migration.js"; - -const setting = (scopeId: string | null, key: string) => - state.pluginRows.find((row) => row.scopeId === scopeId && row.key === key) - ?.value; - -beforeEach(() => { - state.pluginRows = []; - state.hostRows = []; - state.legacyProfiles = 0; - state.adoptedProfiles = 0; - state.pluginInstalled = true; - state.columnGone = false; -}); - -describe("runVaultSettingsMigration", () => { - it("moves every host's profile, keeps the old redirect URI, and a second run changes nothing", async () => { - state.hostRows = [ - { id: 1, vault_profile_id: 7 }, - { id: 2, vault_profile_id: null }, - { id: 3, vault_profile_id: "9" }, - ]; - state.adoptedProfiles = 2; - - const first = await runVaultSettingsMigration(); - expect(first).toEqual({ - hostsMoved: 2, - hostsSkipped: 0, - legacyCallback: true, - }); - expect(setting("1", "profileId")).toBe("7"); - expect(setting("2", "profileId")).toBeUndefined(); - expect(setting("3", "profileId")).toBe("9"); - expect(setting(null, "legacyCallback")).toBe("true"); - - const rows = state.pluginRows.length; - const second = await runVaultSettingsMigration(); - expect(second).toEqual({ - hostsMoved: 0, - hostsSkipped: 2, - legacyCallback: false, - }); - expect(state.pluginRows).toHaveLength(rows); - }); - - it("keeps a host's value already set in the plugin", async () => { - state.pluginRows.push({ - scope: "host", - scopeId: "1", - key: "profileId", - value: "4", - }); - state.hostRows = [{ id: 1, vault_profile_id: 7 }]; - await runVaultSettingsMigration(); - expect(setting("1", "profileId")).toBe("4"); - }); - - it("keeps the old redirect URI for profiles the plugin has not adopted yet", async () => { - state.legacyProfiles = 1; - const result = await runVaultSettingsMigration(); - expect(result.legacyCallback).toBe(true); - }); - - it("uses the new redirect URI on a fresh install, and profiles made later do not change that", async () => { - state.columnGone = true; - const first = await runVaultSettingsMigration(); - expect(first.legacyCallback).toBe(false); - expect(setting(null, "legacyCallback")).toBe("false"); - - state.adoptedProfiles = 3; - await runVaultSettingsMigration(); - expect(setting(null, "legacyCallback")).toBe("false"); - }); - - it("waits for the plugin row", async () => { - state.pluginInstalled = false; - state.hostRows = [{ id: 1, vault_profile_id: 7 }]; - await runVaultSettingsMigration(); - expect(state.pluginRows).toHaveLength(0); - }); -}); diff --git a/src/backend/tests/upgrade/wake-on-lan-settings-migration.test.ts b/src/backend/tests/upgrade/wake-on-lan-settings-migration.test.ts deleted file mode 100644 index fac0de47b..000000000 --- a/src/backend/tests/upgrade/wake-on-lan-settings-migration.test.ts +++ /dev/null @@ -1,190 +0,0 @@ -/** - * The Wake-on-LAN host settings migration. - * - * An upgrade must be lossless: every host with a MAC address or a broadcast - * address must come through into the wake-on-lan plugin's host settings. - * Running it twice must not duplicate or clobber what it moved. - */ - -import { beforeEach, describe, expect, it, vi } from "vitest"; - -const state = vi.hoisted(() => ({ - pluginRows: [] as Array<{ - pluginId: string; - scope: string; - scopeId: string | null; - key: string; - value: string | null; - }>, - hostRows: [] as Array>, - pluginInstalled: true, - columnsGone: false, -})); - -vi.mock("../../database/repositories/factory.js", () => ({ - createCurrentPluginRepository: () => ({ - findById: async (id: string) => - state.pluginInstalled && id === "wake-on-lan" ? { id } : null, - }), - createCurrentPluginSettingsRepository: () => ({ - getAll: async (_pluginId: string, scope: string, scopeId: string | null) => - state.pluginRows.filter( - (row) => row.scope === scope && row.scopeId === scopeId, - ), - set: async ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, - value: string | null, - ) => { - const existing = state.pluginRows.find( - (row) => - row.scope === scope && row.scopeId === scopeId && row.key === key, - ); - if (existing) { - existing.value = value; - return; - } - state.pluginRows.push({ pluginId, scope, scopeId, key, value }); - }, - }), -})); - -vi.mock("../../utils/crypto-migration/raw-rows.js", () => ({ - selectRows: async () => { - if (state.columnsGone) throw new Error("no such column: mac_address"); - return state.hostRows; - }, -})); - -vi.mock("../../utils/logger.js", () => ({ - databaseLogger: { info: vi.fn(), warn: vi.fn() }, -})); - -import { - wakeOnLanSettingsFromRow, - runWakeOnLanSettingsMigration, -} from "../../upgrade/wake-on-lan-settings-migration.js"; - -const hostValues = (hostId: number) => - Object.fromEntries( - state.pluginRows - .filter((row) => row.scope === "host" && row.scopeId === String(hostId)) - .map((row) => [row.key, JSON.parse(row.value ?? "null")]), - ); - -beforeEach(() => { - state.pluginRows = []; - state.hostRows = []; - state.pluginInstalled = true; - state.columnsGone = false; -}); - -describe("wakeOnLanSettingsFromRow", () => { - it("carries over a MAC address", () => { - expect( - wakeOnLanSettingsFromRow({ - id: 1, - mac_address: "aa:bb:cc:dd:ee:ff", - wol_broadcast_address: null, - }), - ).toEqual({ macAddress: "aa:bb:cc:dd:ee:ff" }); - }); - - it("carries over a broadcast address alongside the MAC address", () => { - expect( - wakeOnLanSettingsFromRow({ - id: 1, - mac_address: "aa:bb:cc:dd:ee:ff", - wol_broadcast_address: "192.168.1.255", - }), - ).toEqual({ - macAddress: "aa:bb:cc:dd:ee:ff", - broadcastAddress: "192.168.1.255", - }); - }); - - it("produces nothing for a host with neither set", () => { - expect( - wakeOnLanSettingsFromRow({ - id: 1, - mac_address: null, - wol_broadcast_address: null, - }), - ).toEqual({}); - }); -}); - -describe("runWakeOnLanSettingsMigration", () => { - it("moves every host's wake-on-lan settings and skips hosts with nothing set", async () => { - state.hostRows = [ - { id: 1, mac_address: "aa:bb:cc:dd:ee:ff", wol_broadcast_address: null }, - { - id: 2, - mac_address: "11:22:33:44:55:66", - wol_broadcast_address: "192.168.1.255", - }, - { id: 3, mac_address: null, wol_broadcast_address: null }, - ]; - - const result = await runWakeOnLanSettingsMigration(); - - expect(result).toEqual({ hostsMoved: 2, hostsSkipped: 0 }); - expect(hostValues(1)).toEqual({ macAddress: "aa:bb:cc:dd:ee:ff" }); - expect(hostValues(2)).toEqual({ - macAddress: "11:22:33:44:55:66", - broadcastAddress: "192.168.1.255", - }); - expect(hostValues(3)).toEqual({}); - }); - - it("changes nothing when run twice", async () => { - state.hostRows = [ - { id: 1, mac_address: "aa:bb:cc:dd:ee:ff", wol_broadcast_address: null }, - ]; - await runWakeOnLanSettingsMigration(); - const before = JSON.stringify(state.pluginRows); - - const second = await runWakeOnLanSettingsMigration(); - - expect(second).toEqual({ hostsMoved: 0, hostsSkipped: 1 }); - expect(JSON.stringify(state.pluginRows)).toBe(before); - }); - - it("keeps a value already saved in the plugin", async () => { - state.pluginRows.push({ - pluginId: "wake-on-lan", - scope: "host", - scopeId: "1", - key: "macAddress", - value: JSON.stringify("11:11:11:11:11:11"), - }); - state.hostRows = [ - { id: 1, mac_address: "aa:bb:cc:dd:ee:ff", wol_broadcast_address: null }, - ]; - - await runWakeOnLanSettingsMigration(); - - expect(hostValues(1)).toEqual({ macAddress: "11:11:11:11:11:11" }); - }); - - it("does nothing before the plugin row exists or once the columns are gone", async () => { - state.hostRows = [ - { id: 1, mac_address: "aa:bb:cc:dd:ee:ff", wol_broadcast_address: null }, - ]; - state.pluginInstalled = false; - expect(await runWakeOnLanSettingsMigration()).toEqual({ - hostsMoved: 0, - hostsSkipped: 0, - }); - - state.pluginInstalled = true; - state.columnsGone = true; - expect(await runWakeOnLanSettingsMigration()).toEqual({ - hostsMoved: 0, - hostsSkipped: 0, - }); - expect(state.pluginRows).toEqual([]); - }); -}); diff --git a/src/backend/tests/upgrade/warpgate-settings-migration.test.ts b/src/backend/tests/upgrade/warpgate-settings-migration.test.ts deleted file mode 100644 index 9daabf626..000000000 --- a/src/backend/tests/upgrade/warpgate-settings-migration.test.ts +++ /dev/null @@ -1,161 +0,0 @@ -/** - * The Warpgate host settings migration. - * - * Every host with use_warpgate on, whatever shape the engine returned it in, - * and every pre-2.7 host still on auth type "warpgate", must come through - * into the warpgate plugin's useWarpgate host setting. Running it twice must - * change nothing. - */ - -import { beforeEach, describe, expect, it, vi } from "vitest"; - -const state = vi.hoisted(() => ({ - pluginRows: [] as Array<{ - scope: string; - scopeId: string | null; - key: string; - value: string | null; - }>, - hostRows: [] as Array>, - statements: [] as string[], - pluginInstalled: true, - columnGone: false, -})); - -vi.mock("../../database/repositories/factory.js", () => ({ - createCurrentPluginRepository: () => ({ - findById: async (id: string) => - state.pluginInstalled && id === "warpgate" ? { id } : null, - }), - createCurrentPluginSettingsRepository: () => ({ - get: async ( - _pluginId: string, - scope: string, - scopeId: string | null, - key: string, - ) => - state.pluginRows.find( - (row) => - row.scope === scope && row.scopeId === scopeId && row.key === key, - ) ?? null, - set: async ( - _pluginId: string, - scope: string, - scopeId: string | null, - key: string, - value: string | null, - ) => { - state.pluginRows.push({ scope, scopeId, key, value }); - }, - }), -})); - -vi.mock("../../utils/crypto-migration/raw-rows.js", () => ({ - selectRows: async (query: { queryChunks?: unknown[] }) => { - const text = JSON.stringify(query); - if (text.includes("use_warpgate") && state.columnGone) { - throw new Error("no such column: use_warpgate"); - } - return state.hostRows.filter( - (row) => !text.includes("WHERE") || row.auth_type === "warpgate", - ); - }, - runStatement: async (query: unknown) => { - const text = JSON.stringify(query); - state.statements.push(text); - const id = (query as { queryChunks: unknown[] }).queryChunks.find( - (chunk) => typeof chunk === "number", - ); - const row = state.hostRows.find((candidate) => candidate.id === id); - if (row) row.auth_type = "none"; - }, -})); - -vi.mock("../../utils/logger.js", () => ({ - databaseLogger: { info: vi.fn(), warn: vi.fn() }, -})); - -import { - runWarpgateSettingsMigration, - usesWarpgate, -} from "../../upgrade/warpgate-settings-migration.js"; - -const enabled = (hostId: number) => - state.pluginRows.some( - (row) => - row.scopeId === String(hostId) && - row.key === "useWarpgate" && - row.value === "true", - ); - -beforeEach(() => { - state.pluginRows = []; - state.hostRows = []; - state.statements = []; - state.pluginInstalled = true; - state.columnGone = false; -}); - -describe("usesWarpgate", () => { - it("reads the flag in every shape the engines return", () => { - for (const flag of [true, 1, "1", "true"]) { - expect( - usesWarpgate({ id: 1, use_warpgate: flag, auth_type: "none" }), - ).toBe(true); - } - for (const flag of [false, 0, "0", null]) { - expect( - usesWarpgate({ id: 1, use_warpgate: flag, auth_type: "none" }), - ).toBe(false); - } - expect(usesWarpgate({ id: 1, auth_type: "warpgate" })).toBe(true); - }); -}); - -describe("runWarpgateSettingsMigration", () => { - it("moves every flagged host and fixes the old auth type, and a second run changes nothing", async () => { - state.hostRows = [ - { id: 1, use_warpgate: 1, auth_type: "password" }, - { id: 2, use_warpgate: 0, auth_type: "key" }, - { id: 3, use_warpgate: true, auth_type: "none" }, - { id: 4, use_warpgate: 0, auth_type: "warpgate" }, - ]; - - const first = await runWarpgateSettingsMigration(); - expect(first).toEqual({ - hostsMoved: 3, - hostsSkipped: 0, - authTypesFixed: 1, - }); - expect(enabled(1)).toBe(true); - expect(enabled(2)).toBe(false); - expect(enabled(3)).toBe(true); - expect(enabled(4)).toBe(true); - expect(state.hostRows[3].auth_type).toBe("none"); - - const rows = state.pluginRows.length; - const second = await runWarpgateSettingsMigration(); - // Host 4 is on auth type "none" now, so only the flagged two are seen. - expect(second).toEqual({ - hostsMoved: 0, - hostsSkipped: 2, - authTypesFixed: 0, - }); - expect(state.pluginRows).toHaveLength(rows); - }); - - it("still fixes old auth types on an install without the column", async () => { - state.columnGone = true; - state.hostRows = [{ id: 5, auth_type: "warpgate" }]; - await runWarpgateSettingsMigration(); - expect(enabled(5)).toBe(true); - expect(state.hostRows[0].auth_type).toBe("none"); - }); - - it("waits for the plugin row", async () => { - state.pluginInstalled = false; - state.hostRows = [{ id: 1, use_warpgate: 1, auth_type: "password" }]; - await runWarpgateSettingsMigration(); - expect(state.pluginRows).toHaveLength(0); - }); -}); diff --git a/src/backend/tests/upgrade/web-endpoint-settings-migration.test.ts b/src/backend/tests/upgrade/web-endpoint-settings-migration.test.ts deleted file mode 100644 index d5ce5fea2..000000000 --- a/src/backend/tests/upgrade/web-endpoint-settings-migration.test.ts +++ /dev/null @@ -1,184 +0,0 @@ -/** - * The web endpoint host-columns migration. - * - * An upgrade must be lossless: a host with web endpoints on and a saved - * config must keep both once the ssh_data columns are dropped. Running it - * twice must not duplicate or clobber what it moved. - */ - -import { beforeEach, describe, expect, it, vi } from "vitest"; - -interface HostRow { - id: number; - enable_web_ui: boolean; - web_ui_config: string | null; -} - -interface SettingsRow { - pluginId: string; - scope: string; - scopeId: string | null; - key: string; - value: string | null; -} - -const hostRows: HostRow[] = []; -const settingsRows: SettingsRow[] = []; - -const find = ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, -) => - settingsRows.find( - (row) => - row.pluginId === pluginId && - row.scope === scope && - row.scopeId === scopeId && - row.key === key, - ); - -const pluginSettingsRepository = { - get: async ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, - ) => find(pluginId, scope, scopeId, key) ?? null, - set: async ( - pluginId: string, - scope: string, - scopeId: string | null, - key: string, - value: string | null, - ) => { - const existing = find(pluginId, scope, scopeId, key); - if (existing) { - existing.value = value; - return; - } - settingsRows.push({ pluginId, scope, scopeId, key, value }); - }, -}; - -vi.mock("../../database/repositories/factory.js", () => ({ - createCurrentPluginSettingsRepository: () => pluginSettingsRepository, -})); - -vi.mock("../../database/db/index.js", () => ({ - getDb: () => ({ - all: async () => - hostRows.filter((row) => row.enable_web_ui || row.web_ui_config !== null), - }), -})); - -const { runWebEndpointSettingsMigration } = - await import("../../upgrade/web-endpoint-settings-migration.js"); - -function storedValue(hostId: number, key: string): unknown { - const row = settingsRows.find( - (entry) => entry.scopeId === String(hostId) && entry.key === key, - ); - return row?.value === null || row?.value === undefined - ? undefined - : JSON.parse(row.value); -} - -const endpoint = { - id: "e1", - label: "Proxmox", - scheme: "https", - port: 8006, - path: "/", - access: "direct", - render: "embedded", -}; - -beforeEach(() => { - hostRows.length = 0; - settingsRows.length = 0; -}); - -describe("runWebEndpointSettingsMigration", () => { - it("moves a host's switch and saved config into plugin settings", async () => { - hostRows.push({ - id: 1, - enable_web_ui: true, - web_ui_config: JSON.stringify({ endpoints: [endpoint] }), - }); - - const result = await runWebEndpointSettingsMigration(); - - expect(result.moved).toBe(1); - expect(storedValue(1, "enableWebUi")).toBe(true); - expect(storedValue(1, "webUiConfig")).toEqual({ endpoints: [endpoint] }); - expect(settingsRows.every((row) => row.pluginId === "web-endpoint")).toBe( - true, - ); - expect(settingsRows.every((row) => row.scope === "host")).toBe(true); - }); - - it("keeps a disabled host's saved config and its disabled switch", async () => { - hostRows.push({ - id: 3, - enable_web_ui: false, - web_ui_config: JSON.stringify({ endpoints: [endpoint] }), - }); - - await runWebEndpointSettingsMigration(); - - expect(storedValue(3, "enableWebUi")).toBe(false); - expect(storedValue(3, "webUiConfig")).toEqual({ endpoints: [endpoint] }); - }); - - it("stores an unreadable config as null rather than failing", async () => { - hostRows.push({ id: 4, enable_web_ui: true, web_ui_config: "{nope" }); - - const result = await runWebEndpointSettingsMigration(); - - expect(result.moved).toBe(1); - expect(storedValue(4, "webUiConfig")).toBeNull(); - }); - - it("does nothing on a fresh install", async () => { - const result = await runWebEndpointSettingsMigration(); - - expect(result.moved).toBe(0); - expect(settingsRows).toEqual([]); - }); - - it("is idempotent: a second run changes nothing", async () => { - hostRows.push({ id: 2, enable_web_ui: true, web_ui_config: null }); - - await runWebEndpointSettingsMigration(); - const afterFirst = JSON.parse(JSON.stringify(settingsRows)); - - const second = await runWebEndpointSettingsMigration(); - - expect(second.moved).toBe(0); - expect(second.skipped).toBe(1); - expect(settingsRows).toEqual(afterFirst); - }); - - it("does not overwrite a value the plugin already saved", async () => { - hostRows.push({ - id: 5, - enable_web_ui: true, - web_ui_config: JSON.stringify({ endpoints: [endpoint] }), - }); - await pluginSettingsRepository.set( - "web-endpoint", - "host", - "5", - "enableWebUi", - "false", - ); - - const result = await runWebEndpointSettingsMigration(); - - expect(result.skipped).toBe(1); - expect(storedValue(5, "enableWebUi")).toBe(false); - expect(storedValue(5, "webUiConfig")).toBeUndefined(); - }); -}); diff --git a/src/backend/tests/utils/auth-manager-oidc-logout.test.ts b/src/backend/tests/utils/auth-manager-oidc-logout.test.ts index c3d6eff56..d6d70c06b 100644 --- a/src/backend/tests/utils/auth-manager-oidc-logout.test.ts +++ b/src/backend/tests/utils/auth-manager-oidc-logout.test.ts @@ -75,17 +75,15 @@ function insertSession({ id, userId, sub, sid, providerId }: SessionInput) { .sqlite!.prepare( `INSERT INTO sessions ( id, user_id, jwt_token, device_type, device_info, - oidc_sub, oidc_sid, sso_provider_id, + external_session_ref, created_at, expires_at, last_active_at - ) VALUES (?, ?, ?, 'browser', 'test', ?, ?, ?, ?, ?, ?)`, + ) VALUES (?, ?, ?, 'browser', 'test', ?, ?, ?, ?)`, ) .run( id, userId, `token-${id}`, - sub, - sid, - providerId, + JSON.stringify({ providerId, sub, sid }), "2026-07-10T00:00:00.000Z", "2026-07-11T00:00:00.000Z", "2026-07-10T00:00:00.000Z", @@ -108,9 +106,7 @@ describe("AuthManager.revokeSessionsByExternalSession", () => { jwt_token TEXT NOT NULL, device_type TEXT NOT NULL, device_info TEXT NOT NULL, - oidc_sub TEXT, - oidc_sid TEXT, - sso_provider_id INTEGER, + external_session_ref TEXT, created_at TEXT NOT NULL, expires_at TEXT NOT NULL, last_active_at TEXT NOT NULL @@ -148,7 +144,7 @@ describe("AuthManager.revokeSessionsByExternalSession", () => { await expect( authManager.revokeSessionsByExternalSession({ - ssoProviderId: 7, + providerId: 7, sub: "subject-1", sid: "session-1", }), @@ -182,7 +178,7 @@ describe("AuthManager.revokeSessionsByExternalSession", () => { await expect( authManager.revokeSessionsByExternalSession({ - ssoProviderId: 7, + providerId: 7, sub: "subject-1", }), ).resolves.toBe(2); @@ -190,10 +186,34 @@ describe("AuthManager.revokeSessionsByExternalSession", () => { expect(sessionIds()).toEqual(["other-subject"]); }); + it("leaves sessions with no or a broken reference alone", async () => { + mocks + .sqlite!.prepare( + `INSERT INTO sessions ( + id, user_id, jwt_token, device_type, device_info, + external_session_ref, created_at, expires_at, last_active_at + ) VALUES (?, 'user-1', 't', 'browser', 'test', ?, 'a', 'b', 'c')`, + ) + .run("broken", "{not json"); + mocks + .sqlite!.prepare( + `INSERT INTO sessions ( + id, user_id, jwt_token, device_type, device_info, + external_session_ref, created_at, expires_at, last_active_at + ) VALUES (?, 'user-1', 't', 'browser', 'test', NULL, 'a', 'b', 'c')`, + ) + .run("password"); + + await expect( + authManager.revokeSessionsByExternalSession({ sub: "subject-1" }), + ).resolves.toBe(0); + expect(sessionIds()).toEqual(["broken", "password"]); + }); + it("does not persist when no session matches", async () => { await expect( authManager.revokeSessionsByExternalSession({ - ssoProviderId: 7, + providerId: 7, sid: "missing", }), ).resolves.toBe(0); @@ -213,7 +233,7 @@ describe("AuthManager.revokeSessionsByExternalSession", () => { await expect( authManager.revokeSessionsByExternalSession({ - ssoProviderId: 7, + providerId: 7, sid: "session-1", }), ).rejects.toThrow("disk full"); diff --git a/src/backend/tests/utils/crypto-migration/ssh-options-migration.test.ts b/src/backend/tests/utils/crypto-migration/ssh-options-migration.test.ts deleted file mode 100644 index 5e8aa710a..000000000 --- a/src/backend/tests/utils/crypto-migration/ssh-options-migration.test.ts +++ /dev/null @@ -1,129 +0,0 @@ -/** - * The SSH connection options moving out of ssh_data.terminal_config into - * the core ssh_options column, against a real SQLite database through the - * same raw SQL helpers the boot path uses. - */ - -import Database from "better-sqlite3"; -import { drizzle } from "drizzle-orm/better-sqlite3"; -import { beforeEach, describe, expect, it, vi } from "vitest"; - -const state = vi.hoisted(() => ({ db: null as unknown })); - -vi.mock("../../../database/db/index.js", () => ({ getDb: () => state.db })); -vi.mock("../../../utils/logger.js", () => ({ - databaseLogger: { info: vi.fn(), warn: vi.fn() }, -})); - -const { runSshOptionsMigration } = - await import("../../../utils/crypto-migration/ssh-options-migration.js"); - -let sqlite: Database.Database; - -beforeEach(() => { - sqlite = new Database(":memory:"); - sqlite.exec(` - CREATE TABLE ssh_data ( - id INTEGER PRIMARY KEY, - terminal_config TEXT, - ssh_options TEXT - ); - `); - state.db = drizzle(sqlite); -}); - -function insert(id: number, terminalConfig: unknown, sshOptions?: string) { - sqlite - .prepare( - "INSERT INTO ssh_data (id, terminal_config, ssh_options) VALUES (?, ?, ?)", - ) - .run( - id, - terminalConfig === null ? null : JSON.stringify(terminalConfig), - sshOptions ?? null, - ); -} - -function options(id: number): unknown { - const row = sqlite - .prepare("SELECT ssh_options FROM ssh_data WHERE id = ?") - .get(id) as { ssh_options: string | null }; - return row.ssh_options === null ? null : JSON.parse(row.ssh_options); -} - -describe("runSshOptionsMigration", () => { - it("copies only the connection options, leaving the look and old tunnel keys", async () => { - insert(1, { - theme: "dracula", - fontSize: 16, - keepaliveInterval: 30, - keepaliveCountMax: 3, - allowLegacyAlgorithms: false, - agentSocketPath: "/run/agent.sock", - agentIdentity: "work", - agentForwarding: true, - environmentVariables: [{ key: "LANG", value: "C" }], - startupSnippetId: 4, - cfAccessClientId: "old", - cfTunnelHostname: "old.example.com", - }); - - const result = await runSshOptionsMigration(); - - expect(result.hostsMoved).toBe(1); - expect(options(1)).toEqual({ - keepaliveInterval: 30, - keepaliveCountMax: 3, - allowLegacyAlgorithms: false, - agentSocketPath: "/run/agent.sock", - agentIdentity: "work", - agentForwarding: true, - environmentVariables: [{ key: "LANG", value: "C" }], - }); - const terminalConfig = sqlite - .prepare("SELECT terminal_config FROM ssh_data WHERE id = 1") - .get() as { terminal_config: string }; - expect(JSON.parse(terminalConfig.terminal_config).keepaliveInterval).toBe( - 30, - ); - }); - - it("marks a host with no options as done and skips hosts with no config", async () => { - insert(1, { theme: "dracula" }); - insert(2, null); - - const result = await runSshOptionsMigration(); - - expect(result.hostsMoved).toBe(0); - expect(options(1)).toEqual({}); - expect(options(2)).toBeNull(); - }); - - it("changes nothing on a second run, even after the options were edited", async () => { - insert(1, { keepaliveInterval: 30 }); - insert( - 2, - { keepaliveInterval: 45 }, - JSON.stringify({ keepaliveCountMax: 9 }), - ); - await runSshOptionsMigration(); - - sqlite - .prepare("UPDATE ssh_data SET ssh_options = ? WHERE id = 1") - .run(JSON.stringify({ keepaliveInterval: 90 })); - const second = await runSshOptionsMigration(); - - expect(second.hostsMoved).toBe(0); - expect(options(1)).toEqual({ keepaliveInterval: 90 }); - expect(options(2)).toEqual({ keepaliveCountMax: 9 }); - }); - - it("does not throw on a database without the column", async () => { - sqlite = new Database(":memory:"); - sqlite.exec("CREATE TABLE ssh_data (id INTEGER PRIMARY KEY)"); - state.db = drizzle(sqlite); - await expect(runSshOptionsMigration()).resolves.toEqual({ - hostsMoved: 0, - }); - }); -}); diff --git a/src/backend/tests/utils/host-status-config-migration.test.ts b/src/backend/tests/utils/host-status-config-migration.test.ts deleted file mode 100644 index a1c81038a..000000000 --- a/src/backend/tests/utils/host-status-config-migration.test.ts +++ /dev/null @@ -1,143 +0,0 @@ -/** - * The status half of ssh_data.stats_config moving into the core - * status_check_enabled and status_check_interval columns. Run against a real - * SQLite database through the same raw SQL helpers the boot path uses. - */ - -import Database from "better-sqlite3"; -import { drizzle } from "drizzle-orm/better-sqlite3"; -import { beforeEach, describe, expect, it, vi } from "vitest"; - -const state = vi.hoisted(() => ({ - settings: new Map(), - db: null as unknown, -})); - -vi.mock("../../database/db/index.js", () => ({ getDb: () => state.db })); -vi.mock("../../database/repositories/factory.js", () => ({ - createCurrentSettingsRepository: () => ({ - get: async (key: string) => state.settings.get(key) ?? null, - set: async (key: string, value: string) => { - state.settings.set(key, value); - }, - }), -})); -vi.mock("../../utils/logger.js", () => ({ - databaseLogger: { info: vi.fn(), warn: vi.fn() }, -})); - -const { - runHostStatusConfigMigration, - statusConfigFromStatsConfig, - HOST_STATUS_CONFIG_MIGRATED, -} = - await import("../../utils/crypto-migration/host-status-config-migration.js"); - -let sqlite: Database.Database; - -function createHosts(withStatsConfig = true) { - sqlite = new Database(":memory:"); - sqlite.exec(` - CREATE TABLE ssh_data ( - id INTEGER PRIMARY KEY, - ${withStatsConfig ? "stats_config TEXT," : ""} - status_check_enabled INTEGER NOT NULL DEFAULT 1, - status_check_interval INTEGER - ); - `); - state.db = drizzle(sqlite); -} - -const hosts = () => - sqlite - .prepare( - "SELECT id, status_check_enabled AS enabled, status_check_interval AS interval FROM ssh_data ORDER BY id", - ) - .all(); - -beforeEach(() => { - state.settings.clear(); - createHosts(); -}); - -describe("statusConfigFromStatsConfig", () => { - it.each([ - [null, true, null], - ["{}", true, null], - ['{"statusCheckEnabled":false}', false, null], - ['{"disableTcpPing":true}', false, null], - ['{"useGlobalStatusInterval":false,"statusCheckInterval":45}', true, 45], - ['{"useGlobalStatusInterval":true,"statusCheckInterval":45}', true, null], - ['{"useGlobalStatusInterval":false,"statusCheckInterval":2}', true, null], - [ - JSON.stringify(JSON.stringify({ statusCheckEnabled: false })), - false, - null, - ], - ["not json", true, null], - ])("%s", (raw, enabled, interval) => { - expect(statusConfigFromStatsConfig(raw)).toEqual({ - statusCheckEnabled: enabled, - statusCheckInterval: interval, - }); - }); -}); - -describe("runHostStatusConfigMigration", () => { - it("copies each host's status options into the new columns", async () => { - const insert = sqlite.prepare( - "INSERT INTO ssh_data (id, stats_config) VALUES (?, ?)", - ); - insert.run(1, JSON.stringify({ statusCheckEnabled: false })); - insert.run( - 2, - JSON.stringify({ - useGlobalStatusInterval: false, - statusCheckInterval: 45, - }), - ); - insert.run( - 3, - JSON.stringify({ disableTcpPing: true, metricsEnabled: false }), - ); - insert.run(4, JSON.stringify({ metricsInterval: 10 })); - insert.run(5, null); - - const result = await runHostStatusConfigMigration(); - - expect(result).toEqual({ skipped: false, hostsMoved: 3 }); - expect(hosts()).toEqual([ - { id: 1, enabled: 0, interval: null }, - { id: 2, enabled: 1, interval: 45 }, - { id: 3, enabled: 0, interval: null }, - { id: 4, enabled: 1, interval: null }, - { id: 5, enabled: 1, interval: null }, - ]); - expect(state.settings.get(HOST_STATUS_CONFIG_MIGRATED)).toBe("true"); - }); - - it("changes nothing the second time", async () => { - sqlite - .prepare("INSERT INTO ssh_data (id, stats_config) VALUES (?, ?)") - .run(1, JSON.stringify({ statusCheckEnabled: false })); - await runHostStatusConfigMigration(); - - // The user turns checks back on after the upgrade. - sqlite.exec("UPDATE ssh_data SET status_check_enabled = 1 WHERE id = 1"); - expect(await runHostStatusConfigMigration()).toEqual({ - skipped: true, - hostsMoved: 0, - }); - expect(hosts()).toEqual([{ id: 1, enabled: 1, interval: null }]); - }); - - it("finishes on a fresh install that never had stats_config", async () => { - createHosts(false); - sqlite.exec("INSERT INTO ssh_data (id) VALUES (1)"); - expect(await runHostStatusConfigMigration()).toEqual({ - skipped: false, - hostsMoved: 0, - }); - expect(state.settings.get(HOST_STATUS_CONFIG_MIGRATED)).toBe("true"); - }); -}); diff --git a/src/backend/upgrade/acme-ssl-settings-migration.ts b/src/backend/upgrade/acme-ssl-settings-migration.ts deleted file mode 100644 index 6cac52d7e..000000000 --- a/src/backend/upgrade/acme-ssl-settings-migration.ts +++ /dev/null @@ -1,127 +0,0 @@ -/** - * Moves the 2.8 ACME configuration into the acme-ssl plugin. - * - * 2.8 kept it as one JSON row, `acme_ssl_settings`, in core settings. Each - * field becomes a plugin admin setting. "manual" was never ACME (it meant an - * uploaded certificate, which core still handles), so it turns renewal off. - * The Cloudflare token was plaintext and lands as an encrypted secret; it is - * then removed from the old row. The rest of the row stays for one release. - * - * Idempotent: a field already set in plugin_settings is never overwritten. - */ - -import { databaseLogger } from "../utils/logger.js"; -import { - createCurrentPluginRepository, - createCurrentPluginSettingsRepository, - createCurrentSettingsRepository, -} from "../database/repositories/factory.js"; -import { encryptSystemSecret } from "../utils/system-secret-crypto.js"; - -const PLUGIN_ID = "acme-ssl"; -const LEGACY_KEY = "acme_ssl_settings"; - -export interface AcmeSslSettingsMigrationResult { - moved: string[]; -} - -interface LegacyAcmeSettings { - enabled?: unknown; - domain?: unknown; - email?: unknown; - challengeType?: unknown; - cloudflareToken?: unknown; -} - -const text = (value: unknown) => - typeof value === "string" && value.trim() ? value.trim() : null; - -export async function runAcmeSslSettingsMigration(): Promise { - const result: AcmeSslSettingsMigrationResult = { moved: [] }; - - try { - const plugin = await createCurrentPluginRepository().findById(PLUGIN_ID); - if (!plugin) return result; - - const settings = createCurrentSettingsRepository(); - const raw = await settings.get(LEGACY_KEY); - if (!raw) return result; - - let legacy: LegacyAcmeSettings; - try { - legacy = JSON.parse(raw) as LegacyAcmeSettings; - } catch { - return result; - } - if (!legacy || typeof legacy !== "object") return result; - - const pluginSettings = createCurrentPluginSettingsRepository(); - const writeIfUnset = async ( - field: string, - value: unknown, - secret = false, - ) => { - const existing = await pluginSettings.get( - PLUGIN_ID, - "admin", - null, - field, - ); - if (existing && existing.value !== null) return; - await pluginSettings.set( - PLUGIN_ID, - "admin", - null, - field, - JSON.stringify(value), - secret, - ); - result.moved.push(field); - }; - - const challenge = legacy.challengeType; - const manual = challenge === "manual"; - - const domain = text(legacy.domain); - if (domain) await writeIfUnset("domain", domain); - const email = text(legacy.email); - if (email) await writeIfUnset("email", email); - if (challenge === "dns-cloudflare") { - await writeIfUnset("challengeType", "dns-cloudflare"); - } else if (challenge === "http-webroot") { - await writeIfUnset("challengeType", "http-01"); - } - if (typeof legacy.enabled === "boolean") { - await writeIfUnset("autoRenew", legacy.enabled && !manual); - } - - const token = text(legacy.cloudflareToken); - if (token) { - await writeIfUnset( - "cloudflareToken", - await encryptSystemSecret(token), - true, - ); - const { cloudflareToken: _dropped, ...rest } = legacy; - await settings.set(LEGACY_KEY, JSON.stringify(rest)); - } - - if (result.moved.length > 0) { - databaseLogger.info( - `Moved ${result.moved.length} ACME setting(s) into plugin settings`, - { - operation: "acme_ssl_settings_migration", - moved: result.moved.join(", "), - }, - ); - } - } catch (error) { - // Must not stop the backend: the plugin then shows "not configured". - databaseLogger.warn("ACME settings migration failed", { - operation: "acme_ssl_settings_migration", - error: error instanceof Error ? error.message : String(error), - }); - } - - return result; -} diff --git a/src/backend/upgrade/ai-settings-migration.ts b/src/backend/upgrade/ai-settings-migration.ts index c7f781375..7b7cfb566 100644 --- a/src/backend/upgrade/ai-settings-migration.ts +++ b/src/backend/upgrade/ai-settings-migration.ts @@ -1,22 +1,13 @@ /** - * Moves the AI assistant's settings and provider keys into the ai plugin. + * Moves each AI provider's api_key, encrypted under its owner's data key, + * into a ctx.secrets row "provider:" for that owner, and empties the + * column so no key material is left in the plugin's table. It needs the + * owner's data key, so it runs on every boot until the key opens. * - * - settings.ai_globally_enabled and ai_private_endpoint_allowlist become the - * admin settings globallyEnabled and privateEndpoints (one host per line). - * - user_preferences.ai_assistant_enabled and ai_read_only_commands become - * each user's enabled and allowReadOnlyCommands. - * - ssh_data.enable_ai_assistant becomes the host setting enableAiAssistant. - * - Each provider's api_key, encrypted under its owner's data key, becomes a - * ctx.secrets row "provider:" for that owner, and the column is emptied - * so no key material is left in the plugin's table. - * - * The old columns stay in the database, unused: core's drizzle migrations - * run before this does, so dropping them there would lose the data first. * The providers table is ai_providers until the plugin adopts it and * p_ai_providers after, so both names are tried. * - * Idempotent: nothing is written where the plugin already has a row, and a - * key is only cleared once its copy exists. Safe on every boot. + * Idempotent: a key is only cleared once its copy exists. */ import { sql } from "drizzle-orm"; @@ -24,7 +15,6 @@ import { databaseLogger } from "../utils/logger.js"; import { createCurrentPluginRepository, createCurrentPluginSettingsRepository, - createCurrentSettingsRepository, } from "../database/repositories/factory.js"; import { DataCrypto } from "../utils/data-crypto.js"; import { LazyFieldEncryption } from "../utils/lazy-field-encryption.js"; @@ -37,37 +27,9 @@ import { const PLUGIN_ID = "ai"; export interface AiSettingsMigrationResult { - admin: string[]; - users: number; - hosts: number; keys: number; } -type Flag = number | boolean | string | null | undefined; - -function isTrue(value: Flag): boolean { - return value === true || value === 1 || value === "1" || value === "true"; -} - -function isSet(value: Flag): boolean { - return value !== null && value !== undefined; -} - -/** The legacy JSON array as the textarea the plugin now reads. */ -export function allowlistToText(raw: string): string | null { - try { - const parsed = JSON.parse(raw); - if (!Array.isArray(parsed)) return null; - return parsed - .filter((entry): entry is string => typeof entry === "string") - .map((entry) => entry.trim()) - .filter(Boolean) - .join("\n"); - } catch { - return null; - } -} - async function tryRows(query: ReturnType): Promise { try { return await selectRows(query); @@ -78,12 +40,7 @@ async function tryRows(query: ReturnType): Promise { } export async function runAiSettingsMigration(): Promise { - const result: AiSettingsMigrationResult = { - admin: [], - users: 0, - hosts: 0, - keys: 0, - }; + const result: AiSettingsMigrationResult = { keys: 0 }; // plugin_settings has a foreign key to plugins: nothing to migrate into // until the plugin row exists. @@ -96,91 +53,6 @@ export async function runAiSettingsMigration(): Promise => { - const existing = await pluginSettings.get(PLUGIN_ID, scope, scopeId, key); - if (existing) return false; - await pluginSettings.set( - PLUGIN_ID, - scope, - scopeId, - key, - JSON.stringify(value), - ); - return true; - }; - - try { - const settings = createCurrentSettingsRepository(); - const enabled = await settings.get("ai_globally_enabled"); - if (enabled !== null && enabled !== "") { - if (await setIfMissing("admin", null, "globallyEnabled", isTrue(enabled))) - result.admin.push("globallyEnabled"); - } - const allowlist = await settings.get("ai_private_endpoint_allowlist"); - const text = allowlist ? allowlistToText(allowlist) : null; - if (text !== null) { - if (await setIfMissing("admin", null, "privateEndpoints", text)) - result.admin.push("privateEndpoints"); - } - } catch (error) { - warn("admin settings", error); - } - - const users = await tryRows<{ - user_id: string; - ai_assistant_enabled: Flag; - ai_read_only_commands: Flag; - }>( - sql`SELECT user_id, ai_assistant_enabled, ai_read_only_commands FROM user_preferences`, - ); - for (const row of users) { - try { - let moved = false; - if (isSet(row.ai_assistant_enabled)) { - moved = - (await setIfMissing( - "user", - row.user_id, - "enabled", - isTrue(row.ai_assistant_enabled), - )) || moved; - } - if (isSet(row.ai_read_only_commands)) { - moved = - (await setIfMissing( - "user", - row.user_id, - "allowReadOnlyCommands", - isTrue(row.ai_read_only_commands), - )) || moved; - } - if (moved) result.users++; - } catch (error) { - warn("a user's settings", error); - } - } - - const hosts = await tryRows<{ id: number; enable_ai_assistant: Flag }>( - sql`SELECT id, enable_ai_assistant FROM ssh_data`, - ); - for (const row of hosts) { - if (!isTrue(row.enable_ai_assistant)) continue; - try { - if ( - await setIfMissing("host", String(row.id), "enableAiAssistant", true) - ) { - result.hosts++; - } - } catch (error) { - warn("a host's setting", error); - } - } - for (const table of ["ai_providers", "p_ai_providers"]) { const providers = await tryRows<{ id: number; @@ -227,17 +99,9 @@ export async function runAiSettingsMigration(): Promise 0 || - result.users > 0 || - result.hosts > 0 || - result.keys > 0 - ) { - databaseLogger.info("Moved AI assistant settings into the ai plugin", { + if (result.keys > 0) { + databaseLogger.info("Moved AI provider keys into the ai plugin", { operation: "ai_settings_migration", - admin: result.admin.join(", "), - users: result.users, - hosts: result.hosts, keys: result.keys, }); } diff --git a/src/backend/upgrade/boot-migrations.ts b/src/backend/upgrade/boot-migrations.ts index e517fab03..6c56c0368 100644 --- a/src/backend/upgrade/boot-migrations.ts +++ b/src/backend/upgrade/boot-migrations.ts @@ -1,5 +1,6 @@ /** - * Boot copies of 2.8 data into plugin settings and plugin tables. + * Boot copies of 2.8 data into plugin settings and plugin tables. Only the + * ones that need a user's data key are left: they finish once it opens. * * They run after the plugins are seeded and activated: plugin_settings has a * foreign key to plugins, and some copies write into tables a plugin adopts @@ -10,70 +11,18 @@ import { databaseLogger } from "../utils/logger.js"; import { DatabaseSaveTrigger } from "../utils/database-save-trigger.js"; import { getErrorMessage } from "../utils/error-message.js"; -import { runTailscaleSettingsMigration } from "./tailscale-settings-migration.js"; -import { runProxmoxSettingsMigration } from "./proxmox-settings-migration.js"; -import { runFileManagerSettingsMigration } from "./file-manager-settings-migration.js"; -import { runTunnelsSettingsMigration } from "./tunnels-settings-migration.js"; -import { runWebEndpointSettingsMigration } from "./web-endpoint-settings-migration.js"; -import { runSshTerminalSettingsMigration } from "./ssh-terminal-settings-migration.js"; -import { runSshTerminalLookMigration } from "./ssh-terminal-look-migration.js"; -import { runSshTerminalMacrosMigration } from "./ssh-terminal-macros-migration.js"; -import { runSnippetsSettingsMigration } from "./snippets-settings-migration.js"; -import { runTmuxMonitorSettingsMigration } from "./tmux-monitor-settings-migration.js"; -import { runSessionSharingSettingsMigration } from "./session-sharing-settings-migration.js"; -import { runTelemetrySettingsMigration } from "./telemetry-settings-migration.js"; -import { runSessionRecordingSettingsMigration } from "./session-recording-settings-migration.js"; -import { runRemoteDesktopSettingsMigration } from "./remote-desktop-settings-migration.js"; -import { runHostMetricsSettingsMigration } from "./host-metrics-settings-migration.js"; -import { runDockerSettingsMigration } from "./docker-settings-migration.js"; import { runAiSettingsMigration } from "./ai-settings-migration.js"; -import { runWakeOnLanSettingsMigration } from "./wake-on-lan-settings-migration.js"; -import { runWarpgateSettingsMigration } from "./warpgate-settings-migration.js"; -import { runStepCaSettingsMigration } from "./step-ca-settings-migration.js"; -import { runAcmeSslSettingsMigration } from "./acme-ssl-settings-migration.js"; -import { runVaultSettingsMigration } from "./vault-settings-migration.js"; import { runSecretSourcesTokenMigration } from "./secret-sources-token-migration.js"; import { runTotpMigration } from "./totp-migration.js"; import { runNotificationChannelMigration } from "./notification-channel-migration.js"; import { runTermixIdentityCaMigration } from "./termix-identity-ca-migration.js"; -import { runSsoSettingsMigration } from "./sso-settings-migration.js"; -import { runSsoLegacyIdentityMigration } from "./sso-legacy-identity-migration.js"; -import { runHostDefaultsMigration } from "./host-defaults-migration.js"; const MIGRATIONS: Array<[string, () => Promise]> = [ - ["runTailscaleSettingsMigration", runTailscaleSettingsMigration], - ["runProxmoxSettingsMigration", runProxmoxSettingsMigration], - ["runFileManagerSettingsMigration", runFileManagerSettingsMigration], - ["runTunnelsSettingsMigration", runTunnelsSettingsMigration], - ["runWebEndpointSettingsMigration", runWebEndpointSettingsMigration], - ["runSshTerminalSettingsMigration", runSshTerminalSettingsMigration], - ["runSshTerminalLookMigration", runSshTerminalLookMigration], - ["runSshTerminalMacrosMigration", runSshTerminalMacrosMigration], - ["runSnippetsSettingsMigration", runSnippetsSettingsMigration], - ["runTmuxMonitorSettingsMigration", runTmuxMonitorSettingsMigration], - ["runSessionSharingSettingsMigration", runSessionSharingSettingsMigration], - [ - "runSessionRecordingSettingsMigration", - runSessionRecordingSettingsMigration, - ], - ["runRemoteDesktopSettingsMigration", runRemoteDesktopSettingsMigration], - ["runHostMetricsSettingsMigration", runHostMetricsSettingsMigration], - ["runDockerSettingsMigration", runDockerSettingsMigration], ["runAiSettingsMigration", runAiSettingsMigration], - ["runWakeOnLanSettingsMigration", runWakeOnLanSettingsMigration], - ["runWarpgateSettingsMigration", runWarpgateSettingsMigration], - ["runStepCaSettingsMigration", runStepCaSettingsMigration], - ["runAcmeSslSettingsMigration", runAcmeSslSettingsMigration], - ["runVaultSettingsMigration", runVaultSettingsMigration], ["runSecretSourcesTokenMigration", runSecretSourcesTokenMigration], ["runTotpMigration", runTotpMigration], ["runNotificationChannelMigration", runNotificationChannelMigration], ["runTermixIdentityCaMigration", runTermixIdentityCaMigration], - ["runSsoSettingsMigration", runSsoSettingsMigration], - ["runSsoLegacyIdentityMigration", runSsoLegacyIdentityMigration], - ["runTelemetrySettingsMigration", runTelemetrySettingsMigration], - // Last: it reads what the moves above wrote. - ["runHostDefaultsMigration", runHostDefaultsMigration], ]; export const runPluginDataMigrations = DatabaseSaveTrigger.batched( diff --git a/src/backend/upgrade/docker-settings-migration.ts b/src/backend/upgrade/docker-settings-migration.ts deleted file mode 100644 index 0e0b0d956..000000000 --- a/src/backend/upgrade/docker-settings-migration.ts +++ /dev/null @@ -1,118 +0,0 @@ -/** - * Moves each host's Docker options out of ssh_data and into the docker - * plugin's host settings: enable_docker becomes enableDocker and the runtime - * in docker_config becomes containerRuntime. - * - * The columns stay in the database, unused: core's drizzle migrations run - * before this does, so dropping them there would lose the data first. - * - * Idempotent: a host that already has any docker row is skipped, so this is - * safe on every boot. - */ - -import { sql } from "drizzle-orm"; -import { databaseLogger } from "../utils/logger.js"; -import { - createCurrentPluginRepository, - createCurrentPluginSettingsRepository, -} from "../database/repositories/factory.js"; -import { selectRows } from "../utils/crypto-migration/raw-rows.js"; - -const PLUGIN_ID = "docker"; - -interface LegacyHostRow { - id: number; - enable_docker: number | boolean | string | null; - docker_config: string | null; -} - -/** The plugin's host settings for one legacy row, defaults left out. */ -export function dockerSettingsFromRow( - row: LegacyHostRow, -): Record { - const values: Record = {}; - const flag = row.enable_docker; - if (flag === true || flag === 1 || flag === "1" || flag === "true") { - values.enableDocker = true; - } - if (row.docker_config) { - try { - const config = JSON.parse(row.docker_config) as { runtime?: unknown }; - if (config?.runtime === "podman") values.containerRuntime = "podman"; - } catch { - // unreadable config means the default runtime - } - } - return values; -} - -export interface DockerSettingsMigrationResult { - hostsMoved: number; - hostsSkipped: number; -} - -export async function runDockerSettingsMigration(): Promise { - const result: DockerSettingsMigrationResult = { - hostsMoved: 0, - hostsSkipped: 0, - }; - - // plugin_settings has a foreign key to plugins: nothing to migrate into - // until the plugin row exists. - try { - const plugin = await createCurrentPluginRepository().findById(PLUGIN_ID); - if (!plugin) return result; - } catch { - return result; - } - - const pluginSettings = createCurrentPluginSettingsRepository(); - - let rows: LegacyHostRow[]; - try { - // Raw SQL: schema.ts no longer declares these columns. - rows = await selectRows( - sql`SELECT id, enable_docker, docker_config FROM ssh_data`, - ); - } catch { - // A fresh install never had the columns. - return result; - } - - for (const row of rows) { - const values = dockerSettingsFromRow(row); - if (Object.keys(values).length === 0) continue; - const scopeId = String(row.id); - try { - const existing = await pluginSettings.getAll(PLUGIN_ID, "host", scopeId); - if (existing.length > 0) { - result.hostsSkipped++; - continue; - } - for (const [key, value] of Object.entries(values)) { - await pluginSettings.set( - PLUGIN_ID, - "host", - scopeId, - key, - JSON.stringify(value), - ); - } - result.hostsMoved++; - } catch (error) { - databaseLogger.warn("Docker host settings migration failed for a host", { - operation: "docker_settings_migration", - hostId: row.id, - error: error instanceof Error ? error.message : String(error), - }); - } - } - - if (result.hostsMoved > 0) { - databaseLogger.info( - `Moved Docker settings for ${result.hostsMoved} host(s) into plugin settings`, - { operation: "docker_settings_migration" }, - ); - } - return result; -} diff --git a/src/backend/upgrade/external-auto-provision-migration.ts b/src/backend/upgrade/external-auto-provision-migration.ts new file mode 100644 index 000000000..16317c4c4 --- /dev/null +++ b/src/backend/upgrade/external-auto-provision-migration.ts @@ -0,0 +1,31 @@ +/** + * Renames the auto-provision setting from its 2.8 name, oidc_auto_provision, + * to external_auto_provision: it covers every external login, not only OIDC. + * + * Idempotent: a value already under the new key wins and the old row is + * dropped. + */ + +import { databaseLogger } from "../utils/logger.js"; +import { createCurrentSettingsRepository } from "../database/repositories/factory.js"; +import { EXTERNAL_AUTO_PROVISION_KEY } from "../auth/provisioning.js"; +const LEGACY_KEY = "oidc_auto_provision"; + +export async function runExternalAutoProvisionMigration(): Promise { + try { + const settings = createCurrentSettingsRepository(); + const legacy = await settings.get(LEGACY_KEY); + if (legacy === null) return false; + if ((await settings.get(EXTERNAL_AUTO_PROVISION_KEY)) === null) { + await settings.set(EXTERNAL_AUTO_PROVISION_KEY, legacy); + } + await settings.delete(LEGACY_KEY); + return true; + } catch (error) { + databaseLogger.warn("Auto-provision setting rename failed", { + operation: "external_auto_provision_migration", + error: error instanceof Error ? error.message : String(error), + }); + return false; + } +} diff --git a/src/backend/upgrade/external-identity-migration.ts b/src/backend/upgrade/external-identity-migration.ts deleted file mode 100644 index dae127155..000000000 --- a/src/backend/upgrade/external-identity-migration.ts +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Moves sign-in identities into their own table. - * - * users.oidc_identifier packed provider and subject into one string: - * "ldap::", "github::", or a bare OIDC subject - * whose provider was users.sso_provider_id. Each becomes a - * user_external_identities row; LDAP ones under provider "ldap:", - * the id the ldap plugin signs in with. TOTP enrolment moves in totp-migration.ts. - * - * Lossless: the old column is left as it is. Idempotent: rows that already - * exist are skipped, so it runs on every boot. - */ - -import { databaseLogger } from "../utils/logger.js"; -import { - createCurrentUserAuthRepository, - createCurrentUserRepository, -} from "../database/repositories/factory.js"; - -/** The provider id the sso plugin gives its env-configured provider. */ -const LEGACY_OIDC_PROVIDER_ID = "legacy-oidc"; - -export interface ExternalIdentityMigrationResult { - identities: number; - skipped: number; -} - -/** Splits a stored identifier into provider and subject. */ -export function parseLegacyIdentifier( - identifier: string, - ssoProviderId: number | null | undefined, -): { providerId: string; subject: string } | null { - if (!identifier) return null; - - const prefixed = /^(ldap|github):([^:]+):(.+)$/.exec(identifier); - if (prefixed) { - const provider = prefixed[2]; - const missing = provider === "null" || provider === "undefined"; - // LDAP providers moved to their own table in 2.9 and their identities - // carry the "ldap:" prefix so their ids cannot clash with SSO ones. - if (prefixed[1] === "ldap") { - return missing - ? null - : { providerId: `ldap:${provider}`, subject: prefixed[3] }; - } - return { - providerId: missing ? LEGACY_OIDC_PROVIDER_ID : provider, - subject: prefixed[3], - }; - } - - return { - providerId: - ssoProviderId !== null && ssoProviderId !== undefined - ? String(ssoProviderId) - : LEGACY_OIDC_PROVIDER_ID, - subject: identifier, - }; -} - -export async function runExternalIdentityMigration(): Promise { - const result: ExternalIdentityMigrationResult = { - identities: 0, - skipped: 0, - }; - - try { - const users = await createCurrentUserRepository().listAll(); - const auth = createCurrentUserAuthRepository(); - - for (const user of users) { - if (user.oidcIdentifier) { - const parsed = parseLegacyIdentifier( - user.oidcIdentifier, - user.ssoProviderId, - ); - if (parsed) { - const existing = await auth.findIdentity( - parsed.providerId, - parsed.subject, - ); - // sso-legacy-identity-migration.ts moves "legacy-oidc" rows to - // their provider; recreating one would undo that on every boot. - const moved = - !existing && - parsed.providerId === LEGACY_OIDC_PROVIDER_ID && - (await auth.listIdentitiesForUser(user.id)).some( - (link) => link.subject === parsed.subject, - ); - if (existing || moved) { - result.skipped++; - } else { - await auth.linkIdentity({ - userId: user.id, - providerId: parsed.providerId, - subject: parsed.subject, - }); - result.identities++; - } - } - } - } - - if (result.identities > 0) { - databaseLogger.info("Moved sign-in identities", { - operation: "external_identity_migration", - ...result, - }); - } - } catch (error) { - databaseLogger.error("External identity migration failed", error, { - operation: "external_identity_migration", - }); - } - - return result; -} diff --git a/src/backend/upgrade/file-manager-settings-migration.ts b/src/backend/upgrade/file-manager-settings-migration.ts deleted file mode 100644 index e5acf70da..000000000 --- a/src/backend/upgrade/file-manager-settings-migration.ts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Moves the three file-manager host columns into the file-manager plugin's - * host-scope settings, before those columns are dropped from ssh_data. - * - * Idempotent: skips a host that already has a file-manager settings row, so - * it is safe to run on every boot. Lossless: reads straight off the - * still-present ssh_data columns, so it must run and finish before those - * columns are ever dropped from schema.ts. - */ - -import { sql } from "drizzle-orm"; -import { databaseLogger } from "../utils/logger.js"; -import { - legacyFlag, - selectLegacyRows, -} from "../utils/crypto-migration/raw-rows.js"; -import { createCurrentPluginSettingsRepository } from "../database/repositories/factory.js"; - -export interface FileManagerSettingsMigrationResult { - moved: number; - skipped: number; -} - -interface LegacyFileManagerRow { - id: number; - enable_file_manager: number | boolean | null; - default_path: string | null; - scp_legacy: number | boolean | null; -} - -export async function runFileManagerSettingsMigration(): Promise { - const result: FileManagerSettingsMigrationResult = { moved: 0, skipped: 0 }; - - try { - // Raw SQL, not the typed schema: this runs once schema.ts has already - // dropped these columns, so the query is the only thing left that still - // knows they used to exist. - const rows = await selectLegacyRows(sql` - SELECT id, enable_file_manager, default_path, scp_legacy - FROM ssh_data - WHERE enable_file_manager = false OR default_path IS NOT NULL - OR scp_legacy = true - `); - - if (rows.length === 0) return result; - - const pluginSettingsRepository = createCurrentPluginSettingsRepository(); - - for (const row of rows) { - const scopeId = String(row.id); - const existing = await pluginSettingsRepository.get( - "file-manager", - "host", - scopeId, - "enableFileManager", - ); - if (existing && existing.value !== null) { - result.skipped++; - continue; - } - - await pluginSettingsRepository.set( - "file-manager", - "host", - scopeId, - "enableFileManager", - JSON.stringify(legacyFlag(row.enable_file_manager, true)), - ); - await pluginSettingsRepository.set( - "file-manager", - "host", - scopeId, - "defaultPath", - JSON.stringify(row.default_path ?? null), - ); - await pluginSettingsRepository.set( - "file-manager", - "host", - scopeId, - "scpLegacy", - JSON.stringify(!!row.scp_legacy), - ); - result.moved++; - } - - if (result.moved > 0) { - databaseLogger.info( - `Moved file manager settings for ${result.moved} host(s) into plugin settings`, - { operation: "file_manager_settings_migration", moved: result.moved }, - ); - } - } catch (error) { - // A failed migration must not stop the backend. Hosts simply keep - // whatever file-manager settings they already had (none, on a fresh run). - databaseLogger.warn("File manager settings migration failed", { - operation: "file_manager_settings_migration", - error: error instanceof Error ? error.message : String(error), - }); - } - - return result; -} diff --git a/src/backend/upgrade/host-defaults-migration.ts b/src/backend/upgrade/host-defaults-migration.ts deleted file mode 100644 index ed38a57b4..000000000 --- a/src/backend/upgrade/host-defaults-migration.ts +++ /dev/null @@ -1,447 +0,0 @@ -/** - * Moves 2.8 and early 2.9 default settings into host defaults, and gets hosts - * ready to be classified against them. - * - * Levels, each moved once: - * - core `settings.host_defaults` (SOCKS5 and the status check) becomes the - * server level. Its credential and SOCKS5 password are dropped: a - * credential belongs to one user and a password is never a default. - * - a folder's credential becomes that folder's auth default. - * - ssh-terminal's admin "new host" look, tmux and command history become the - * server level; each user's terminal defaults, local echo and link click - * setting become their user level. - * - host-metrics' "enabled for new hosts" becomes the server level. - * - remote-desktop's per-user display settings become the user level. - * - * Hosts: a host saved before host defaults gets the value it really used - * written in, where that was not its stored value. A terminal that - * followed the user's look used the user's look; an echo mode of "default" - * used the user's setting; a credential host with no credential used its - * folder's; a remote desktop used the connecting user's display settings. - * The classify pass that follows compares those against the new levels, so - * nothing a user sees changes. - * - * The old rows stay in place for a downgrade until 26.10.0. - */ - -import type { PluginManifest } from "@termix/plugin-sdk/manifest"; -import { - createCurrentHostDefaultsRepository, - createCurrentPluginSettingsRepository, - createCurrentSettingsRepository, -} from "../database/repositories/factory.js"; -import type { HostRow } from "../database/repositories/host-defaults-repository.js"; -import { hostSettingsPlugins } from "../database/routes/host-plugin-settings.js"; -import { - CORE_NAMESPACE, - defaultValuesEqual, - normalizeSocks5Default, - parseDefaultOverrides, -} from "../../types/host-defaults.js"; -import { encodeDefaultValue } from "../hosts/defaults/levels.js"; -import { folderChainPaths } from "../hosts/defaults/resolve.js"; -import { databaseLogger } from "../utils/logger.js"; - -const TERMINAL = "ssh-terminal"; -const METRICS = "host-metrics"; -const DESKTOP = "remote-desktop"; - -const TERMINAL_APPEARANCE_KEYS = [ - "theme", - "cursorBlink", - "cursorStyle", - "fontSize", - "fontFamily", - "scrollback", - "letterSpacing", - "lineHeight", - "bellStyle", - "minimumContrastRatio", - "backgroundImage", - "backgroundImageOpacity", - "customThemeColors", -]; - -/** ssh-terminal admin key -> the host key it seeded. */ -const TERMINAL_NEW_HOST_KEYS: Record = { - commandHistoryForNewHosts: "enableCommandHistory", - newHostTheme: "theme", - newHostFontFamily: "fontFamily", - newHostFontSize: "fontSize", - newHostCursorStyle: "cursorStyle", - newHostCursorBlink: "cursorBlink", - newHostAutoTmux: "autoTmux", -}; - -/** ssh-terminal keys whose "default" meant the user's own setting. */ -const TERMINAL_USER_MODES: Record = { - localEcho: "auto", - linkClickBehavior: "confirm", -}; - -const DESKTOP_DISPLAY_KEYS = [ - "colorDepth", - "resizeMethod", - "forceLossless", - "enableWallpaper", - "enableFontSmoothing", - "enableDesktopComposition", - "disableAudio", - "enablePrinting", - "enableDrive", - "disableCopy", - "disablePaste", -]; - -const MARKER_PREFIX = "host_defaults_moved_"; - -function decode(raw: string | null | undefined): unknown { - if (raw === null || raw === undefined) return undefined; - try { - let parsed = JSON.parse(raw); - if (typeof parsed === "string" && /^[[{]/.test(parsed)) { - parsed = JSON.parse(parsed); - } - return parsed; - } catch { - return undefined; - } -} - -function asObject(value: unknown): Record | null { - return value && typeof value === "object" && !Array.isArray(value) - ? (value as Record) - : null; -} - -async function once(name: string, run: () => Promise): Promise { - const settings = createCurrentSettingsRepository(); - const marker = `${MARKER_PREFIX}${name}`; - if (await settings.get(marker)) return; - await run(); - await settings.set(marker, new Date().toISOString()); -} - -type Level = - | { level: "admin" } - | { level: "user"; userId: string } - | { level: "folder"; userId: string; folderId: number }; - -async function setDefault( - scope: Level, - namespace: string, - key: string, - value: unknown, -): Promise { - const repository = createCurrentHostDefaultsRepository(); - const existing = await repository.listScope(scope); - if (existing.some((row) => row.namespace === namespace && row.key === key)) { - return; - } - await repository.apply( - scope, - [{ namespace, key, value: encodeDefaultValue(value) }], - [], - null, - ); -} - -async function moveCoreLevels(): Promise { - const raw = await createCurrentSettingsRepository().get("host_defaults"); - const stored = asObject(decode(raw)) ?? {}; - if (stored.useSocks5 === true) { - await setDefault( - { level: "admin" }, - CORE_NAMESPACE, - "socks5", - normalizeSocks5Default({ ...stored, socks5ProxyChain: null }), - ); - } - if (stored.statusCheckEnabled === false) { - await setDefault( - { level: "admin" }, - CORE_NAMESPACE, - "statusCheckEnabled", - false, - ); - } - if (stored.credentialId !== undefined || stored.socks5Password) { - databaseLogger.info( - "Host defaults no longer hold a credential or proxy password; set a credential per user or folder instead", - { operation: "host_defaults_migration" }, - ); - } -} - -async function moveFolderCredentials(): Promise { - const { createCurrentRepositoryContext } = - await import("../database/repositories/factory.js"); - const { sshFolders } = await import("../database/db/schema.js"); - const { isNotNull } = await import("drizzle-orm"); - const rows = await createCurrentRepositoryContext() - .drizzle.select({ - id: sshFolders.id, - userId: sshFolders.userId, - credentialId: sshFolders.credentialId, - }) - .from(sshFolders) - .where(isNotNull(sshFolders.credentialId)); - for (const row of rows) { - await setDefault( - { level: "folder", userId: row.userId, folderId: row.id }, - CORE_NAMESPACE, - "auth", - { - authType: "credential", - credentialId: row.credentialId, - overrideCredentialUsername: false, - agentSocketPath: null, - agentIdentity: null, - }, - ); - } -} - -async function storedRows( - pluginId: string, - scope: "admin" | "user", - key: string, -) { - return ( - await createCurrentPluginSettingsRepository().listByKey( - pluginId, - scope, - key, - ) - ).filter((row) => row.value !== null); -} - -async function moveTerminalLevels(): Promise { - for (const [adminKey, hostKey] of Object.entries(TERMINAL_NEW_HOST_KEYS)) { - for (const row of await storedRows(TERMINAL, "admin", adminKey)) { - const value = decode(row.value); - if (value === undefined || value === null) continue; - await setDefault({ level: "admin" }, TERMINAL, hostKey, value); - } - } - for (const row of await storedRows(TERMINAL, "user", "terminalDefaults")) { - const values = asObject(decode(row.value)); - if (!values || !row.scopeId) continue; - for (const key of TERMINAL_APPEARANCE_KEYS) { - if (values[key] === undefined) continue; - await setDefault( - { level: "user", userId: row.scopeId }, - TERMINAL, - key, - values[key], - ); - } - } - for (const key of Object.keys(TERMINAL_USER_MODES)) { - for (const row of await storedRows(TERMINAL, "user", key)) { - const value = decode(row.value); - if (typeof value !== "string" || !row.scopeId) continue; - await setDefault( - { level: "user", userId: row.scopeId }, - TERMINAL, - key, - value, - ); - } - } -} - -async function moveMetricsLevels(): Promise { - for (const row of await storedRows(METRICS, "admin", "enabledForNewHosts")) { - if (decode(row.value) === false) { - await setDefault({ level: "admin" }, METRICS, "metricsEnabled", false); - } - } -} - -async function moveDesktopLevels(): Promise { - for (const key of DESKTOP_DISPLAY_KEYS) { - for (const row of await storedRows(DESKTOP, "user", key)) { - const value = decode(row.value); - if (typeof value !== "string" || value === "inherit" || !row.scopeId) { - continue; - } - await setDefault( - { level: "user", userId: row.scopeId }, - DESKTOP, - key, - value, - ); - } - } -} - -/** - * Hosts saved before host defaults (by an older release, or 2.8 after a - * downgrade), which still carry the old meaning. A host saved since has its - * overrides set, and a namespace it lacks is classified as it stands. - */ -function unclassified(rows: HostRow[]): HostRow[] { - return rows.filter( - (row) => - !row.sharedSource && parseDefaultOverrides(row.defaultOverrides) === null, - ); -} - -async function userValues( - pluginId: string, - key: string, -): Promise> { - const result = new Map(); - for (const row of await storedRows(pluginId, "user", key)) { - if (row.scopeId) result.set(row.scopeId, decode(row.value)); - } - return result; -} - -function manifestDefault(manifest: PluginManifest, key: string): unknown { - return manifest.contributes?.settings?.host?.fields.find( - (field) => field.key === key, - )?.default; -} - -/** - * Writes one host value, or removes the row when it is the manifest default - * so the host reads that default the usual way. - */ -async function writeHostValue( - manifest: PluginManifest, - hostId: number, - key: string, - value: unknown, -): Promise { - const settings = createCurrentPluginSettingsRepository(); - if (defaultValuesEqual(value, manifestDefault(manifest, key))) { - await settings.delete(manifest.id, "host", String(hostId), key); - } else { - await settings.set( - manifest.id, - "host", - String(hostId), - key, - JSON.stringify(value), - ); - } -} - -async function bakeTerminalHosts( - manifest: PluginManifest, - rows: HostRow[], -): Promise { - const hosts = unclassified(rows); - if (hosts.length === 0) return; - const settings = createCurrentPluginSettingsRepository(); - const lookByUser = await userValues(TERMINAL, "terminalDefaults"); - const modesByUser = new Map( - await Promise.all( - Object.keys(TERMINAL_USER_MODES).map( - async (key) => [key, await userValues(TERMINAL, key)] as const, - ), - ), - ); - - for (const host of hosts) { - const stored = new Map( - (await settings.getAll(TERMINAL, "host", String(host.id))).map((row) => [ - row.key, - decode(row.value), - ]), - ); - if (stored.get("inheritAppearance") !== false) { - const look = asObject(lookByUser.get(host.userId)) ?? {}; - for (const key of TERMINAL_APPEARANCE_KEYS) { - const value = look[key] ?? manifestDefault(manifest, key); - await writeHostValue(manifest, host.id, key, value); - } - } - for (const [key, fallback] of Object.entries(TERMINAL_USER_MODES)) { - const own = stored.get(key); - if (own !== undefined && own !== "default") continue; - const user = modesByUser.get(key)?.get(host.userId); - await writeHostValue( - manifest, - host.id, - key, - typeof user === "string" && user !== "default" ? user : fallback, - ); - } - } -} - -async function bakeDesktopHosts( - manifest: PluginManifest, - rows: HostRow[], -): Promise { - const hosts = unclassified(rows); - if (hosts.length === 0) return; - const byKey = new Map( - await Promise.all( - DESKTOP_DISPLAY_KEYS.map( - async (key) => [key, await userValues(DESKTOP, key)] as const, - ), - ), - ); - for (const host of hosts) { - for (const key of DESKTOP_DISPLAY_KEYS) { - const value = byKey.get(key)?.get(host.userId); - if (typeof value !== "string" || value === "inherit") continue; - await writeHostValue(manifest, host.id, key, value); - } - } -} - -/** A credential host with no credential of its own used its folder's. */ -async function bakeFolderCredentials(rows: HostRow[]): Promise { - const hosts = unclassified(rows).filter( - (row) => row.authType === "credential" && !row.credentialId && row.folder, - ); - if (hosts.length === 0) return; - const repository = createCurrentHostDefaultsRepository(); - const { createCurrentHostResolutionRepository } = - await import("../database/repositories/factory.js"); - const resolution = createCurrentHostResolutionRepository(); - const patches: Array<{ id: number; values: Partial }> = []; - for (const host of hosts) { - if (folderChainPaths(host.folder).length === 0) continue; - const credentialId = await resolution.findFolderCredentialId( - host.userId, - host.folder!, - ); - if (credentialId) patches.push({ id: host.id, values: { credentialId } }); - } - await repository.updateHosts(patches); -} - -export async function runHostDefaultsMigration(): Promise { - const manifests = new Map( - hostSettingsPlugins().map((manifest) => [manifest.id, manifest]), - ); - try { - await once("core", async () => { - await moveCoreLevels(); - await moveFolderCredentials(); - }); - if (manifests.has(TERMINAL)) await once(TERMINAL, moveTerminalLevels); - if (manifests.has(METRICS)) await once(METRICS, moveMetricsLevels); - if (manifests.has(DESKTOP)) await once(DESKTOP, moveDesktopLevels); - - const rows = await createCurrentHostDefaultsRepository().listHosts({ - all: true, - }); - await bakeFolderCredentials(rows); - const terminal = manifests.get(TERMINAL); - if (terminal) await bakeTerminalHosts(terminal, rows); - const desktop = manifests.get(DESKTOP); - if (desktop) await bakeDesktopHosts(desktop, rows); - } catch (error) { - databaseLogger.warn("Host defaults migration failed", { - operation: "host_defaults_migration", - error: error instanceof Error ? error.message : String(error), - }); - } -} diff --git a/src/backend/upgrade/host-metrics-settings-migration.ts b/src/backend/upgrade/host-metrics-settings-migration.ts deleted file mode 100644 index e72d274b8..000000000 --- a/src/backend/upgrade/host-metrics-settings-migration.ts +++ /dev/null @@ -1,182 +0,0 @@ -/** - * Moves Host Metrics' settings out of core and into the host-metrics - * plugin's own settings. - * - * Admin keys from `settings`: global_metrics_interval, - * metrics_history_retention_days and metricsEnabled inside host_defaults (the - * default for new hosts). Per host, the metrics half of - * ssh_data.stats_config: metricsEnabled, the host's own interval, the enabled - * widgets and the mount lists. The status half is core and moved by - * host-status-config-migration.ts. - * - * The old settings rows and the column stay in place, unused. - * - * Idempotent: an admin key already set, or a host that already has any - * host-metrics row, is skipped, so this is safe on every boot. - */ - -import { sql } from "drizzle-orm"; -import { databaseLogger } from "../utils/logger.js"; -import { - createCurrentPluginRepository, - createCurrentPluginSettingsRepository, - createCurrentSettingsRepository, -} from "../database/repositories/factory.js"; -import { selectRows } from "../utils/crypto-migration/raw-rows.js"; - -const PLUGIN_ID = "host-metrics"; - -/** A JSON object stored as text, or {} when it is anything else. */ -function parseStatsConfig(raw: string | null): Record { - try { - let parsed: unknown = raw ? JSON.parse(raw) : null; - if (typeof parsed === "string") parsed = JSON.parse(parsed); - return parsed && typeof parsed === "object" && !Array.isArray(parsed) - ? (parsed as Record) - : {}; - } catch { - return {}; - } -} - -function seconds(value: unknown, min: number, max: number): number | null { - const parsed = Number(value); - return Number.isInteger(parsed) && parsed >= min && parsed <= max - ? parsed - : null; -} - -/** The plugin's host settings for one stats_config value, defaults left out. */ -export function hostSettingsFromStatsConfig( - raw: string | null, -): Record { - const config = parseStatsConfig(raw); - const values: Record = {}; - if (config.metricsEnabled === false) values.metricsEnabled = false; - if (config.useGlobalMetricsInterval === false) { - const interval = seconds(config.metricsInterval, 5, 3600); - if (interval !== null) values.metricsInterval = interval; - } - if (Array.isArray(config.enabledWidgets)) { - values.enabledWidgets = config.enabledWidgets.filter( - (widget): widget is string => typeof widget === "string", - ); - } - if (Array.isArray(config.excludedMounts) && config.excludedMounts.length) { - values.excludedMounts = config.excludedMounts.filter( - (mount): mount is string => typeof mount === "string", - ); - } - if (Array.isArray(config.monitoredMounts) && config.monitoredMounts.length) { - values.monitoredMounts = config.monitoredMounts.filter( - (mount) => - !!mount && - typeof mount === "object" && - typeof (mount as { path?: unknown }).path === "string", - ); - } - return values; -} - -export interface HostMetricsSettingsMigrationResult { - moved: string[]; - hostsMoved: number; - hostsSkipped: number; -} - -export async function runHostMetricsSettingsMigration(): Promise { - const result: HostMetricsSettingsMigrationResult = { - moved: [], - hostsMoved: 0, - hostsSkipped: 0, - }; - - // plugin_settings has a foreign key to plugins: nothing to migrate into - // until the plugin row exists. - try { - const plugin = await createCurrentPluginRepository().findById(PLUGIN_ID); - if (!plugin) return result; - } catch { - return result; - } - - const pluginSettings = createCurrentPluginSettingsRepository(); - const write = ( - scope: "admin" | "host", - scopeId: string | null, - key: string, - value: unknown, - ) => - pluginSettings.set(PLUGIN_ID, scope, scopeId, key, JSON.stringify(value)); - - try { - const settings = createCurrentSettingsRepository(); - const moves: [string, string, number, number][] = [ - ["global_metrics_interval", "metricsInterval", 5, 3600], - ["metrics_history_retention_days", "historyRetentionDays", 1, 90], - ]; - for (const [legacyKey, field, min, max] of moves) { - const value = seconds(await settings.get(legacyKey), min, max); - if (value === null) continue; - const existing = await pluginSettings.get( - PLUGIN_ID, - "admin", - null, - field, - ); - if (existing && existing.value !== null) continue; - await write("admin", null, field, value); - result.moved.push(legacyKey); - } - - const defaults = parseStatsConfig(await settings.get("host_defaults")); - if (defaults.metricsEnabled === false) { - const existing = await pluginSettings.get( - PLUGIN_ID, - "admin", - null, - "enabledForNewHosts", - ); - if (!existing || existing.value === null) { - await write("admin", null, "enabledForNewHosts", false); - result.moved.push("host_defaults.metricsEnabled"); - } - } - } catch (error) { - databaseLogger.warn("Host Metrics admin settings migration failed", { - operation: "host_metrics_settings_migration", - error: error instanceof Error ? error.message : String(error), - }); - } - - try { - // Raw SQL: schema.ts no longer declares stats_config. - const rows = await selectRows<{ id: number; stats_config: string | null }>( - sql`SELECT id, stats_config FROM ssh_data WHERE stats_config IS NOT NULL`, - ); - for (const row of rows) { - const values = hostSettingsFromStatsConfig(row.stats_config); - if (Object.keys(values).length === 0) continue; - const scopeId = String(row.id); - const existing = await pluginSettings.getAll(PLUGIN_ID, "host", scopeId); - if (existing.length > 0) { - result.hostsSkipped++; - continue; - } - for (const [key, value] of Object.entries(values)) { - await write("host", scopeId, key, value); - } - result.hostsMoved++; - } - } catch { - // A fresh install never had the column. - } - - if (result.moved.length > 0 || result.hostsMoved > 0) { - databaseLogger.info( - `Moved ${result.moved.length} Host Metrics setting(s) and ${result.hostsMoved} host(s) into plugin settings`, - { operation: "host_metrics_settings_migration" }, - ); - } - return result; -} diff --git a/src/backend/upgrade/opkssh-config-migration.ts b/src/backend/upgrade/opkssh-config-migration.ts index da26c2ecb..a4ec657fc 100644 --- a/src/backend/upgrade/opkssh-config-migration.ts +++ b/src/backend/upgrade/opkssh-config-migration.ts @@ -3,27 +3,20 @@ * * The config file moves from DATA_DIR/.opk/config.yml to the plugin's own * folder (DATA_DIR/plugin-data/opkssh/config.yml). The old file is copied, not - * moved, so a downgrade still finds it. An install that had a config also - * keeps sending the pre-2.9 redirect URI (/host/opkssh-callback), because - * that is the one registered with its identity providers: the plugin's - * legacyCallback admin setting is turned on. + * moved, so a downgrade still finds it. * - * Idempotent: an existing target file or setting is never overwritten. + * Idempotent: an existing target file is never overwritten. */ import { promises as fs } from "node:fs"; import path from "node:path"; import { databaseLogger } from "../utils/logger.js"; -import { - createCurrentPluginRepository, - createCurrentPluginSettingsRepository, -} from "../database/repositories/factory.js"; +import { createCurrentPluginRepository } from "../database/repositories/factory.js"; const PLUGIN_ID = "opkssh"; export interface OpksshConfigMigrationResult { copied: boolean; - legacyCallback: boolean; } async function exists(file: string): Promise { @@ -39,10 +32,7 @@ export async function runOpksshConfigMigration( dataDir: string = process.env.DATA_DIR || path.join(process.cwd(), "db", "data"), ): Promise { - const result: OpksshConfigMigrationResult = { - copied: false, - legacyCallback: false, - }; + const result: OpksshConfigMigrationResult = { copied: false }; const legacyConfig = path.join(dataDir, ".opk", "config.yml"); if (!(await exists(legacyConfig))) return result; @@ -61,24 +51,6 @@ export async function runOpksshConfigMigration( await fs.copyFile(legacyConfig, target); result.copied = true; } - - const settings = createCurrentPluginSettingsRepository(); - const current = await settings.get( - PLUGIN_ID, - "admin", - null, - "legacyCallback", - ); - if (!current) { - await settings.set( - PLUGIN_ID, - "admin", - null, - "legacyCallback", - JSON.stringify(true), - ); - result.legacyCallback = true; - } } catch (error) { databaseLogger.warn("OPKSSH config migration failed", { operation: "opkssh_config_migration", diff --git a/src/backend/upgrade/plugin-data-moves.ts b/src/backend/upgrade/plugin-data-moves.ts index db05952bc..558b8d48f 100644 --- a/src/backend/upgrade/plugin-data-moves.ts +++ b/src/backend/upgrade/plugin-data-moves.ts @@ -9,16 +9,14 @@ import { runLdapProviderMigration } from "./ldap-provider-migration.js"; import { runOpksshConfigMigration } from "./opkssh-config-migration.js"; import { runTermixIdentityCaMigration } from "./termix-identity-ca-migration.js"; import { runSessionRecordingDataMigration } from "./session-recording-data-migration.js"; -import { runHostMetricsSettingsMigration } from "./host-metrics-settings-migration.js"; import { runNotificationChannelMigration } from "./notification-channel-migration.js"; export async function runPluginDataMoves(): Promise { await runLdapProviderMigration(); await runOpksshConfigMigration(); await runTermixIdentityCaMigration(); - // Both set how long history is kept, which the plugin prunes by on activate. + // Sets how long history is kept, which the plugin prunes by on activate. await runSessionRecordingDataMigration(); - await runHostMetricsSettingsMigration(); // Automations name channels by id, so they are in place before it starts. await runNotificationChannelMigration(); } diff --git a/src/backend/upgrade/proxmox-settings-migration.ts b/src/backend/upgrade/proxmox-settings-migration.ts deleted file mode 100644 index 55085cd41..000000000 --- a/src/backend/upgrade/proxmox-settings-migration.ts +++ /dev/null @@ -1,119 +0,0 @@ -/** - * Moves the four Proxmox host columns into the proxmox plugin's host-scope - * settings, before those columns are dropped from ssh_data. - * - * Idempotent: skips a host that already has a proxmox settings row, so it is - * safe to run on every boot. Lossless: reads straight off the still-present - * ssh_data columns, so it must run and finish before those columns are ever - * dropped from schema.ts. - */ - -import { sql } from "drizzle-orm"; -import { databaseLogger } from "../utils/logger.js"; -import { selectLegacyRows } from "../utils/crypto-migration/raw-rows.js"; -import { createCurrentPluginSettingsRepository } from "../database/repositories/factory.js"; - -export interface ProxmoxSettingsMigrationResult { - moved: number; - skipped: number; -} - -interface LegacyProxmoxRow { - id: number; - enable_proxmox: number | boolean | null; - proxmox_config: string | null; - enable_proxmox_stats: number | boolean | null; - proxmox_stats_config: string | null; -} - -export async function runProxmoxSettingsMigration(): Promise { - const result: ProxmoxSettingsMigrationResult = { moved: 0, skipped: 0 }; - - try { - // Raw SQL, not the typed schema: this runs once schema.ts has already - // dropped these columns, so the query is the only thing left that still - // knows they used to exist. - const rows = await selectLegacyRows(sql` - SELECT id, enable_proxmox, proxmox_config, enable_proxmox_stats, proxmox_stats_config - FROM ssh_data - WHERE enable_proxmox = true OR proxmox_config IS NOT NULL - OR enable_proxmox_stats = true OR proxmox_stats_config IS NOT NULL - `); - - if (rows.length === 0) return result; - - const pluginSettingsRepository = createCurrentPluginSettingsRepository(); - - for (const row of rows) { - const scopeId = String(row.id); - const existing = await pluginSettingsRepository.get( - "proxmox", - "host", - scopeId, - "enableProxmox", - ); - if (existing && existing.value !== null) { - result.skipped++; - continue; - } - - await pluginSettingsRepository.set( - "proxmox", - "host", - scopeId, - "enableProxmox", - JSON.stringify(!!row.enable_proxmox), - ); - await pluginSettingsRepository.set( - "proxmox", - "host", - scopeId, - "proxmoxConfig", - JSON.stringify( - row.proxmox_config ? safeParse(row.proxmox_config) : null, - ), - ); - await pluginSettingsRepository.set( - "proxmox", - "host", - scopeId, - "enableProxmoxStats", - JSON.stringify(!!row.enable_proxmox_stats), - ); - await pluginSettingsRepository.set( - "proxmox", - "host", - scopeId, - "proxmoxStatsConfig", - JSON.stringify( - row.proxmox_stats_config ? safeParse(row.proxmox_stats_config) : null, - ), - ); - result.moved++; - } - - if (result.moved > 0) { - databaseLogger.info( - `Moved Proxmox settings for ${result.moved} host(s) into plugin settings`, - { operation: "proxmox_settings_migration", moved: result.moved }, - ); - } - } catch (error) { - // A failed migration must not stop the backend. Hosts simply keep - // whatever proxmox settings they already had (none, on a fresh run). - databaseLogger.warn("Proxmox settings migration failed", { - operation: "proxmox_settings_migration", - error: error instanceof Error ? error.message : String(error), - }); - } - - return result; -} - -function safeParse(raw: string): unknown { - try { - return JSON.parse(raw); - } catch { - return null; - } -} diff --git a/src/backend/upgrade/remote-desktop-settings-migration.ts b/src/backend/upgrade/remote-desktop-settings-migration.ts deleted file mode 100644 index 6d8dc7d80..000000000 --- a/src/backend/upgrade/remote-desktop-settings-migration.ts +++ /dev/null @@ -1,289 +0,0 @@ -/** - * Moves Remote Desktop's settings out of core and into the remote-desktop - * plugin's own settings. - * - * Admin keys from `settings`: guac_enabled and guac_url. Per user, the RDP - * defaults JSON in user_preferences.rdp_defaults. Per host, the option - * columns in ssh_data: the three protocol switches and ports, rdp_security, - * rdp_ignore_cert and guacamole_config, folding in the older security, - * ignore_cert and connection_type-only shapes, plus enable_terminal_toolbar, - * which also drove the remote desktop toolbar. - * - * The logins (users, passwords, credential ids, auth types, domain) stay in - * core, next to the host, where sharing needs them. - * - * The columns stay in the database, unused: core's drizzle migrations run - * before this does, so dropping them there would lose the data first. - * - * Idempotent: an admin key already set, or a user or host that already has - * any remote-desktop row, is skipped, so this is safe on every boot. - */ - -import { sql } from "drizzle-orm"; -import { databaseLogger } from "../utils/logger.js"; -import { - createCurrentPluginRepository, - createCurrentPluginSettingsRepository, - createCurrentSettingsRepository, -} from "../database/repositories/factory.js"; -import { - runStatement, - selectRows, -} from "../utils/crypto-migration/raw-rows.js"; - -const PLUGIN_ID = "remote-desktop"; -const PROTOCOLS = ["rdp", "vnc", "telnet"] as const; -const DEFAULT_PORTS = { rdp: 3389, vnc: 5900, telnet: 23 }; - -type Flag = number | boolean | string | null | undefined; - -/** true, 1 and "1" are on; null means the column was never set. */ -function flag(value: Flag): boolean | undefined { - if (value === null || value === undefined) return undefined; - return value === true || value === 1 || value === "1" || value === "true"; -} - -function port(value: unknown): number | undefined { - const parsed = Number(value); - return Number.isInteger(parsed) && parsed > 0 && parsed <= 65535 - ? parsed - : undefined; -} - -function jsonObject(value: unknown): Record | undefined { - if (typeof value !== "string" || !value) return undefined; - try { - const parsed = JSON.parse(value); - return parsed && typeof parsed === "object" && !Array.isArray(parsed) - ? parsed - : undefined; - } catch { - return undefined; - } -} - -interface LegacyHostRow { - id: number; - port: number | null; - connection_type: string | null; - enable_rdp: Flag; - enable_vnc: Flag; - enable_telnet: Flag; - rdp_port: number | null; - vnc_port: number | null; - telnet_port: number | null; - rdp_security: string | null; - rdp_ignore_cert: Flag; - security: string | null; - ignore_cert: Flag; - guacamole_config: string | null; - enable_terminal_toolbar: Flag; -} - -/** The plugin's host settings for one legacy row, defaults left out. */ -export function hostSettingsFromRow( - row: LegacyHostRow, -): Record { - const values: Record = {}; - const flags = { - rdp: flag(row.enable_rdp) === true, - vnc: flag(row.enable_vnc) === true, - telnet: flag(row.enable_telnet) === true, - }; - const legacyType = PROTOCOLS.find((p) => p === row.connection_type); - // Hosts from before the per-protocol switches only had connection_type. - if (!flags.rdp && !flags.vnc && !flags.telnet && legacyType) { - flags[legacyType] = true; - } - const ports = { - rdp: port(row.rdp_port), - vnc: port(row.vnc_port), - telnet: port(row.telnet_port), - }; - if (legacyType && ports[legacyType] === undefined) { - ports[legacyType] = port(row.port); - } - - for (const protocol of PROTOCOLS) { - const key = `enable${protocol[0].toUpperCase()}${protocol.slice(1)}`; - if (flags[protocol]) values[key] = true; - const value = ports[protocol]; - if (value !== undefined && value !== DEFAULT_PORTS[protocol]) { - values[`${protocol}Port`] = value; - } - } - - const security = row.rdp_security || row.security; - if (security) values.rdpSecurity = security; - const ignoreCert = flag(row.rdp_ignore_cert) ?? flag(row.ignore_cert); - if (ignoreCert) values.rdpIgnoreCert = true; - const config = jsonObject(row.guacamole_config); - if (config && Object.keys(config).length > 0) values.guacamoleConfig = config; - if (flag(row.enable_terminal_toolbar) === false) values.enableToolbar = false; - return values; -} - -/** Old camelCase defaults JSON -> the plugin's user settings. */ -function userSettingsFromDefaults( - defaults: Record, -): Record { - const values: Record = {}; - const depth = Number(defaults.colorDepth); - if ([16, 24, 32].includes(depth)) values.colorDepth = String(depth); - if ( - defaults.resizeMethod === "display-update" || - defaults.resizeMethod === "reconnect" - ) { - values.resizeMethod = defaults.resizeMethod; - } - for (const key of [ - "forceLossless", - "enableWallpaper", - "enableFontSmoothing", - "enableDesktopComposition", - "disableAudio", - "enablePrinting", - "enableDrive", - "disableCopy", - "disablePaste", - ]) { - if (typeof defaults[key] === "boolean") { - values[key] = defaults[key] ? "on" : "off"; - } - } - return values; -} - -export interface RemoteDesktopSettingsMigrationResult { - moved: string[]; - usersMoved: number; - hostsMoved: number; - hostsSkipped: number; -} - -export async function runRemoteDesktopSettingsMigration(): Promise { - const result: RemoteDesktopSettingsMigrationResult = { - moved: [], - usersMoved: 0, - hostsMoved: 0, - hostsSkipped: 0, - }; - - // plugin_settings has a foreign key to plugins: nothing to migrate into - // until the plugin row exists. - try { - const plugin = await createCurrentPluginRepository().findById(PLUGIN_ID); - if (!plugin) return result; - } catch { - return result; - } - - const pluginSettings = createCurrentPluginSettingsRepository(); - const write = ( - scope: "admin" | "user" | "host", - scopeId: string | null, - key: string, - value: unknown, - ) => - pluginSettings.set(PLUGIN_ID, scope, scopeId, key, JSON.stringify(value)); - - try { - const settings = createCurrentSettingsRepository(); - const moves: [string, string, (raw: string) => unknown][] = [ - ["guac_enabled", "enabled", (raw) => raw !== "false" && raw !== "0"], - ["guac_url", "guacdUrl", (raw) => raw], - ]; - for (const [legacyKey, field, parse] of moves) { - const legacy = await settings.get(legacyKey); - if (legacy === null || legacy === "") continue; - const existing = await pluginSettings.get( - PLUGIN_ID, - "admin", - null, - field, - ); - if (existing && existing.value !== null) continue; - await write("admin", null, field, parse(legacy)); - result.moved.push(legacyKey); - } - } catch (error) { - databaseLogger.warn("Remote Desktop admin settings migration failed", { - operation: "remote_desktop_settings_migration", - error: error instanceof Error ? error.message : String(error), - }); - } - - try { - const rows = await selectRows<{ - user_id: string; - rdp_defaults: string | null; - }>(sql`SELECT user_id, rdp_defaults FROM user_preferences`); - for (const row of rows) { - const defaults = jsonObject(row.rdp_defaults); - if (!defaults) continue; - const values = userSettingsFromDefaults(defaults); - if (Object.keys(values).length === 0) continue; - const existing = await pluginSettings.getAll( - PLUGIN_ID, - "user", - row.user_id, - ); - if (existing.length > 0) continue; - for (const [key, value] of Object.entries(values)) { - await write("user", row.user_id, key, value); - } - result.usersMoved++; - } - } catch { - // A fresh install never had the column. - } - - try { - // Raw SQL: schema.ts no longer declares these columns. - const rows = await selectRows(sql` - SELECT id, port, connection_type, enable_rdp, enable_vnc, enable_telnet, - rdp_port, vnc_port, telnet_port, rdp_security, rdp_ignore_cert, - security, ignore_cert, guacamole_config, enable_terminal_toolbar - FROM ssh_data - `); - for (const row of rows) { - const values = hostSettingsFromRow(row); - if (Object.keys(values).length === 0) continue; - const scopeId = String(row.id); - const existing = await pluginSettings.getAll(PLUGIN_ID, "host", scopeId); - if (existing.length > 0) { - result.hostsSkipped++; - continue; - } - for (const [key, value] of Object.entries(values)) { - await write("host", scopeId, key, value); - } - result.hostsMoved++; - } - - // Those same connection_type-only hosts kept the enable_ssh default of - // on, which core used to correct on every read. FALSE and NOT read the - // same on SQLite's integers and on real booleans. - await runStatement(sql` - UPDATE ssh_data SET enable_ssh = FALSE - WHERE connection_type IN ('rdp', 'vnc', 'telnet') - AND (enable_rdp IS NULL OR NOT enable_rdp) - AND (enable_vnc IS NULL OR NOT enable_vnc) - AND (enable_telnet IS NULL OR NOT enable_telnet) - `); - } catch { - // A fresh install never had the columns. - } - - if ( - result.moved.length > 0 || - result.usersMoved > 0 || - result.hostsMoved > 0 - ) { - databaseLogger.info( - `Moved ${result.moved.length} Remote Desktop setting(s), ${result.usersMoved} user(s) and ${result.hostsMoved} host(s) into plugin settings`, - { operation: "remote_desktop_settings_migration" }, - ); - } - return result; -} diff --git a/src/backend/upgrade/session-recording-settings-migration.ts b/src/backend/upgrade/session-recording-settings-migration.ts deleted file mode 100644 index 99a4e9dc4..000000000 --- a/src/backend/upgrade/session-recording-settings-migration.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Moves the session logging host column into the session-recording plugin's - * host-scope settings, before the column is dropped from ssh_data. - * - * The legacy column defaulted to true; the plugin's enable switch defaults to - * false like every other plugin's. So unlike a migration whose old and new - * defaults agree, every host needs an explicit row here, not just the ones - * that turned the feature off. - * - * Idempotent: skips a host that already has a session-recording settings row, - * so it is safe to run on every boot. - */ - -import { sql } from "drizzle-orm"; -import { databaseLogger } from "../utils/logger.js"; -import { - legacyFlag, - selectLegacyRows, -} from "../utils/crypto-migration/raw-rows.js"; -import { createCurrentPluginSettingsRepository } from "../database/repositories/factory.js"; - -export interface SessionRecordingSettingsMigrationResult { - moved: number; - skipped: number; -} - -interface LegacySessionLoggingRow { - id: number; - enable_session_logging: number | boolean | null; -} - -export async function runSessionRecordingSettingsMigration(): Promise { - const result: SessionRecordingSettingsMigrationResult = { - moved: 0, - skipped: 0, - }; - - try { - // Raw SQL, not the typed schema, so this keeps working once schema.ts - // stops declaring this column. - const rows = await selectLegacyRows(sql` - SELECT id, enable_session_logging FROM ssh_data - `); - - if (rows.length === 0) return result; - - const pluginSettingsRepository = createCurrentPluginSettingsRepository(); - - for (const row of rows) { - const scopeId = String(row.id); - const existing = await pluginSettingsRepository.get( - "session-recording", - "host", - scopeId, - "enableSessionRecording", - ); - if (existing && existing.value !== null) { - result.skipped++; - continue; - } - - await pluginSettingsRepository.set( - "session-recording", - "host", - scopeId, - "enableSessionRecording", - JSON.stringify(legacyFlag(row.enable_session_logging, true)), - ); - result.moved++; - } - - if (result.moved > 0) { - databaseLogger.info( - `Moved session recording settings for ${result.moved} host(s) into plugin settings`, - { - operation: "session_recording_settings_migration", - moved: result.moved, - }, - ); - } - } catch (error) { - // A failed migration must not stop the backend. Hosts simply keep the - // plugin's own default (off) until this runs successfully. - databaseLogger.warn("Session recording settings migration failed", { - operation: "session_recording_settings_migration", - error: error instanceof Error ? error.message : String(error), - }); - } - - return result; -} diff --git a/src/backend/upgrade/session-sharing-settings-migration.ts b/src/backend/upgrade/session-sharing-settings-migration.ts deleted file mode 100644 index 8baa13891..000000000 --- a/src/backend/upgrade/session-sharing-settings-migration.ts +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Moves session sharing's two settings into the session-sharing plugin: - * the instance-wide switch (settings.session_sharing_globally_enabled) into - * its admin setting, and ssh_data.allow_session_sharing into its host setting. - * - * Idempotent: nothing is written where the plugin already has a row, so it is - * safe on every boot. Both default to on, so only a switch that was turned off - * needs a row. The legacy settings row is left in place for a release. - */ - -import { sql } from "drizzle-orm"; -import { databaseLogger } from "../utils/logger.js"; -import { selectLegacyRows } from "../utils/crypto-migration/raw-rows.js"; -import { - createCurrentPluginSettingsRepository, - createCurrentSettingsRepository, -} from "../database/repositories/factory.js"; - -const PLUGIN_ID = "session-sharing"; -const LEGACY_GLOBAL_KEY = "session_sharing_globally_enabled"; - -export interface SessionSharingSettingsMigrationResult { - movedGlobal: boolean; - movedHosts: number; - skipped: number; -} - -interface LegacyHostRow { - id: number; -} - -export async function runSessionSharingSettingsMigration(): Promise { - const result: SessionSharingSettingsMigrationResult = { - movedGlobal: false, - movedHosts: 0, - skipped: 0, - }; - - try { - const pluginSettings = createCurrentPluginSettingsRepository(); - - const legacy = - await createCurrentSettingsRepository().get(LEGACY_GLOBAL_KEY); - if (legacy === "false") { - const existing = await pluginSettings.get( - PLUGIN_ID, - "admin", - null, - "globallyEnabled", - ); - if (existing && existing.value !== null) { - result.skipped++; - } else { - await pluginSettings.set( - PLUGIN_ID, - "admin", - null, - "globallyEnabled", - JSON.stringify(false), - ); - result.movedGlobal = true; - } - } - - // Raw SQL, so this keeps working once schema.ts stops declaring the column. - const rows = await selectLegacyRows(sql` - SELECT id FROM ssh_data WHERE allow_session_sharing = false - `); - for (const row of rows) { - const scopeId = String(row.id); - const existing = await pluginSettings.get( - PLUGIN_ID, - "host", - scopeId, - "allowSessionSharing", - ); - if (existing && existing.value !== null) { - result.skipped++; - continue; - } - await pluginSettings.set( - PLUGIN_ID, - "host", - scopeId, - "allowSessionSharing", - JSON.stringify(false), - ); - result.movedHosts++; - } - - if (result.movedGlobal || result.movedHosts > 0) { - databaseLogger.info("Moved session sharing settings into the plugin", { - operation: "session_sharing_settings_migration", - movedGlobal: result.movedGlobal, - movedHosts: result.movedHosts, - }); - } - } catch (error) { - // Never stops the backend. Sharing then stays on, the plugin's default. - databaseLogger.warn("Session sharing settings migration failed", { - operation: "session_sharing_settings_migration", - error: error instanceof Error ? error.message : String(error), - }); - } - - return result; -} diff --git a/src/backend/upgrade/snippets-settings-migration.ts b/src/backend/upgrade/snippets-settings-migration.ts deleted file mode 100644 index c9a694a4f..000000000 --- a/src/backend/upgrade/snippets-settings-migration.ts +++ /dev/null @@ -1,185 +0,0 @@ -/** - * Moves what core kept for snippets into the snippets plugin's settings. - * - * Per host: ssh_data.quick_actions becomes the quickActions host setting, - * and terminal_config.startupSnippetId the startupSnippetId one. Per user: - * user_preferences.confirm_snippet_execution and folders_collapsed become the - * confirmExecution and foldersCollapsed user settings. - * - * The columns keep their values until 26.10.0 drops them. - * - * Idempotent: a host that already has either host setting, and a user key - * already set, are skipped, so this is safe on every boot. - */ - -import { sql } from "drizzle-orm"; -import { databaseLogger } from "../utils/logger.js"; -import { - legacyFlag, - selectLegacyRows, -} from "../utils/crypto-migration/raw-rows.js"; -import { - createCurrentPluginRepository, - createCurrentPluginSettingsRepository, -} from "../database/repositories/factory.js"; - -const PLUGIN_ID = "snippets"; -const HOST_KEYS = ["quickActions", "startupSnippetId"]; - -function parseJson(value: unknown): unknown { - if (typeof value !== "string" || !value) return value ?? undefined; - try { - let parsed = JSON.parse(value); - if (typeof parsed === "string") parsed = JSON.parse(parsed); - return parsed; - } catch { - return undefined; - } -} - -function snippetId(value: unknown): number | null { - const id = typeof value === "string" ? Number(value) : value; - return typeof id === "number" && Number.isInteger(id) && id > 0 ? id : null; -} - -/** The snippets host settings one 2.8 ssh_data row carried. */ -export function snippetHostSettingsFromRow(row: { - quick_actions?: unknown; - terminal_config?: unknown; -}): Record { - const values: Record = {}; - const actions = parseJson(row.quick_actions); - if (Array.isArray(actions)) { - const quickActions = actions.flatMap((entry) => { - const id = snippetId((entry as { snippetId?: unknown })?.snippetId); - if (id === null) return []; - const name = (entry as { name?: unknown }).name; - return [{ name: typeof name === "string" ? name : "", snippetId: id }]; - }); - if (quickActions.length > 0) values.quickActions = quickActions; - } - const config = parseJson(row.terminal_config) as - { startupSnippetId?: unknown } | undefined; - const startup = snippetId(config?.startupSnippetId); - if (startup !== null) values.startupSnippetId = startup; - return values; -} - -export interface SnippetsSettingsMigrationResult { - hostsMoved: number; - hostsSkipped: number; - usersMoved: number; -} - -export async function runSnippetsSettingsMigration(): Promise { - const result: SnippetsSettingsMigrationResult = { - hostsMoved: 0, - hostsSkipped: 0, - usersMoved: 0, - }; - - try { - const plugin = await createCurrentPluginRepository().findById(PLUGIN_ID); - if (!plugin) return result; - } catch { - return result; - } - - const pluginSettings = createCurrentPluginSettingsRepository(); - - try { - const rows = await selectLegacyRows<{ - id: number; - quick_actions: unknown; - terminal_config: unknown; - }>(sql` - SELECT id, quick_actions, terminal_config FROM ssh_data - WHERE quick_actions IS NOT NULL OR terminal_config IS NOT NULL - `); - for (const row of rows) { - const values = snippetHostSettingsFromRow(row); - if (Object.keys(values).length === 0) continue; - const scopeId = String(row.id); - let done = false; - for (const key of HOST_KEYS) { - if (await pluginSettings.get(PLUGIN_ID, "host", scopeId, key)) { - done = true; - } - } - if (done) { - result.hostsSkipped++; - continue; - } - for (const [key, value] of Object.entries(values)) { - await pluginSettings.set( - PLUGIN_ID, - "host", - scopeId, - key, - JSON.stringify(value), - ); - } - result.hostsMoved++; - } - } catch (error) { - databaseLogger.warn("Snippets host settings migration failed", { - operation: "snippets_settings_migration", - error: error instanceof Error ? error.message : String(error), - }); - } - - try { - const rows = await selectLegacyRows<{ - user_id: string; - confirm_snippet_execution: unknown; - folders_collapsed: unknown; - }>(sql` - SELECT user_id, confirm_snippet_execution, folders_collapsed - FROM user_preferences - `); - for (const row of rows) { - const values: Record = {}; - if (row.confirm_snippet_execution != null) { - values.confirmExecution = legacyFlag( - row.confirm_snippet_execution, - false, - ); - } - if (row.folders_collapsed != null) { - values.foldersCollapsed = legacyFlag(row.folders_collapsed, true); - } - let moved = false; - for (const [key, value] of Object.entries(values)) { - const existing = await pluginSettings.get( - PLUGIN_ID, - "user", - row.user_id, - key, - ); - if (existing && existing.value !== null) continue; - await pluginSettings.set( - PLUGIN_ID, - "user", - row.user_id, - key, - JSON.stringify(value), - ); - moved = true; - } - if (moved) result.usersMoved++; - } - } catch (error) { - databaseLogger.warn("Snippets user settings migration failed", { - operation: "snippets_settings_migration", - error: error instanceof Error ? error.message : String(error), - }); - } - - if (result.hostsMoved > 0 || result.usersMoved > 0) { - databaseLogger.info( - `Moved the snippet settings of ${result.hostsMoved} host(s) and ${result.usersMoved} user(s) into plugin settings`, - { operation: "snippets_settings_migration" }, - ); - } - return result; -} diff --git a/src/backend/upgrade/ssh-terminal-look-migration.ts b/src/backend/upgrade/ssh-terminal-look-migration.ts deleted file mode 100644 index 2a5b34701..000000000 --- a/src/backend/upgrade/ssh-terminal-look-migration.ts +++ /dev/null @@ -1,318 +0,0 @@ -/** - * Moves the terminal's look and behavior out of core and into the - * ssh-terminal plugin's settings. - * - * Per host, the terminal keys of ssh_data.terminal_config become host - * settings: every behavior key the host saved (tmux, mosh, auto-fill, link - * and echo modes, syntax highlighting and the rest), and its look only when - * it saved one. 2.8 stripped the look from a host that followed the user's - * defaults, so a host with any look key had opted out: it gets - * inheritAppearance false and its look copied. The SSH connection options in - * the same JSON are core's and move to ssh_data.ssh_options - * (ssh-options-migration.ts); startupSnippetId and a legacy sudo password - * stay where they are. - * - * Per user, user_preferences.terminal_defaults, custom_themes and - * command_autocomplete become user settings. - * - * The columns keep their values until 26.10.0 drops them. - * - * Idempotent: a host that already has any of these rows, and a user key that - * is already set, are skipped, so this is safe on every boot. - */ - -import { sql } from "drizzle-orm"; -import { databaseLogger } from "../utils/logger.js"; -import { selectLegacyRows } from "../utils/crypto-migration/raw-rows.js"; -import { - createCurrentPluginRepository, - createCurrentPluginSettingsRepository, -} from "../database/repositories/factory.js"; - -const PLUGIN_ID = "ssh-terminal"; - -const APPEARANCE_KEYS = [ - "theme", - "cursorBlink", - "cursorStyle", - "fontSize", - "fontFamily", - "scrollback", - "letterSpacing", - "lineHeight", - "bellStyle", - "minimumContrastRatio", - "backgroundImage", - "backgroundImageOpacity", - "customThemeColors", -]; - -const BEHAVIOR_KEYS = [ - "rightClickSelectsWord", - "macOptionIsMeta", - "fastScrollModifier", - "fastScrollSensitivity", - "backspaceMode", - "autoMosh", - "moshCommand", - "autoTmux", - "useSSHTitle", - "syntaxHighlighting", - "syntaxHighlightingOptions", - "linkClickBehavior", - "localEcho", - "passwordPromptAutoFill", - "sudoPasswordAutoFill", -]; - -const HOST_KEYS = ["inheritAppearance", ...APPEARANCE_KEYS, ...BEHAVIOR_KEYS]; - -/** The plugin's select fields, which refuse anything outside their options. */ -const ENUMS: Record = { - cursorStyle: ["block", "underline", "bar"], - bellStyle: ["none", "sound", "visual", "both"], - fastScrollModifier: ["alt", "ctrl", "shift"], - backspaceMode: ["normal", "control-h"], - linkClickBehavior: ["default", "confirm", "direct"], - localEcho: ["default", "off", "auto", "on"], -}; - -const NUMBER_KEYS = new Set([ - "fontSize", - "scrollback", - "letterSpacing", - "lineHeight", - "minimumContrastRatio", - "backgroundImageOpacity", - "fastScrollSensitivity", -]); - -const LEGACY_TERMIX_THEMES = [ - "Termix Dark", - "Termix Light", - "termixDark", - "termixLight", -]; - -function isObject(value: unknown): value is Record { - return !!value && typeof value === "object" && !Array.isArray(value); -} - -function parseJson(value: unknown): unknown { - if (typeof value !== "string" || !value) return undefined; - try { - let parsed = JSON.parse(value); - // Some rows were stringified twice. - if (typeof parsed === "string") parsed = JSON.parse(parsed); - return parsed; - } catch { - return undefined; - } -} - -/** One key's value in the type its plugin field declares, else undefined. */ -export function terminalValue(key: string, value: unknown): unknown { - if (value === undefined) return undefined; - if (ENUMS[key]) { - return typeof value === "string" && ENUMS[key].includes(value) - ? value - : undefined; - } - if (NUMBER_KEYS.has(key)) { - if (value === null || value === "") return undefined; - const parsed = Number(value); - return Number.isFinite(parsed) ? parsed : undefined; - } - switch (key) { - case "theme": - if (typeof value !== "string" || !value) return undefined; - return LEGACY_TERMIX_THEMES.includes(value) ? "termix" : value; - case "fontFamily": - return typeof value === "string" && value ? value : undefined; - case "backgroundImage": - case "moshCommand": - return value === null - ? "" - : typeof value === "string" - ? value - : undefined; - case "customThemeColors": - return isObject(value) ? value : undefined; - case "syntaxHighlightingOptions": - return isObject(value) ? value : undefined; - default: - return typeof value === "boolean" ? value : undefined; - } -} - -/** The ssh-terminal host settings one 2.8 terminal_config value carried. */ -export function hostSettingsFromTerminalConfig( - raw: unknown, -): Record { - const config = typeof raw === "string" ? parseJson(raw) : raw; - if (!isObject(config)) return {}; - const values: Record = {}; - for (const key of BEHAVIOR_KEYS) { - const value = terminalValue(key, config[key]); - if (value !== undefined) values[key] = value; - } - if (APPEARANCE_KEYS.some((key) => key in config)) { - values.inheritAppearance = false; - for (const key of APPEARANCE_KEYS) { - const value = terminalValue(key, config[key]); - if (value !== undefined) values[key] = value; - } - } - return values; -} - -/** The ssh-terminal user settings one user_preferences row carried. */ -export function userSettingsFromPreferences(row: { - terminal_defaults?: unknown; - custom_themes?: unknown; - command_autocomplete?: unknown; -}): Record { - const values: Record = {}; - const defaults = parseJson(row.terminal_defaults); - if (isObject(defaults)) { - const picked: Record = {}; - for (const key of APPEARANCE_KEYS) { - const value = terminalValue(key, defaults[key]); - if (value !== undefined) picked[key] = value; - } - if (Object.keys(picked).length > 0) values.terminalDefaults = picked; - } - const themes = parseJson(row.custom_themes); - if (Array.isArray(themes) && themes.length > 0) values.customThemes = themes; - const autocomplete = row.command_autocomplete; - if (autocomplete !== null && autocomplete !== undefined) { - values.commandAutocomplete = - autocomplete === true || - autocomplete === 1 || - autocomplete === "1" || - autocomplete === "true"; - } - return values; -} - -export interface SshTerminalLookMigrationResult { - hostsMoved: number; - hostsSkipped: number; - usersMoved: number; -} - -export async function runSshTerminalLookMigration(): Promise { - const result: SshTerminalLookMigrationResult = { - hostsMoved: 0, - hostsSkipped: 0, - usersMoved: 0, - }; - - // plugin_settings has a foreign key to plugins: nothing to migrate into - // until the plugin row exists. - try { - const plugin = await createCurrentPluginRepository().findById(PLUGIN_ID); - if (!plugin) return result; - } catch { - return result; - } - - const pluginSettings = createCurrentPluginSettingsRepository(); - - try { - const rows = await selectLegacyRows<{ - id: number; - terminal_config: string | null; - }>(sql` - SELECT id, terminal_config FROM ssh_data - WHERE terminal_config IS NOT NULL - `); - const moves = rows - .map((row) => ({ - scopeId: String(row.id), - values: hostSettingsFromTerminalConfig(row.terminal_config), - })) - .filter((move) => Object.keys(move.values).length > 0); - // A few reads for every candidate host rather than one per host. - const done = new Set(); - for (let i = 0; i < moves.length; i += 500) { - const existing = await pluginSettings.getAllForScopeIds( - "host", - moves.slice(i, i + 500).map((move) => move.scopeId), - ); - for (const setting of existing) { - if (setting.pluginId === PLUGIN_ID && HOST_KEYS.includes(setting.key)) { - done.add(setting.scopeId); - } - } - } - for (const { scopeId, values } of moves) { - if (done.has(scopeId)) { - result.hostsSkipped++; - continue; - } - for (const [key, value] of Object.entries(values)) { - await pluginSettings.set( - PLUGIN_ID, - "host", - scopeId, - key, - JSON.stringify(value), - ); - } - result.hostsMoved++; - } - } catch (error) { - databaseLogger.warn("SSH terminal host look migration failed", { - operation: "ssh_terminal_look_migration", - error: error instanceof Error ? error.message : String(error), - }); - } - - try { - const rows = await selectLegacyRows<{ - user_id: string; - terminal_defaults: string | null; - custom_themes: string | null; - command_autocomplete: unknown; - }>(sql` - SELECT user_id, terminal_defaults, custom_themes, command_autocomplete - FROM user_preferences - `); - for (const row of rows) { - const values = userSettingsFromPreferences(row); - let moved = false; - for (const [key, value] of Object.entries(values)) { - const existing = await pluginSettings.get( - PLUGIN_ID, - "user", - row.user_id, - key, - ); - if (existing && existing.value !== null) continue; - await pluginSettings.set( - PLUGIN_ID, - "user", - row.user_id, - key, - JSON.stringify(value), - ); - moved = true; - } - if (moved) result.usersMoved++; - } - } catch (error) { - databaseLogger.warn("SSH terminal user settings migration failed", { - operation: "ssh_terminal_look_migration", - error: error instanceof Error ? error.message : String(error), - }); - } - - if (result.hostsMoved > 0 || result.usersMoved > 0) { - databaseLogger.info( - `Moved the terminal settings of ${result.hostsMoved} host(s) and ${result.usersMoved} user(s) into plugin settings`, - { operation: "ssh_terminal_look_migration" }, - ); - } - return result; -} diff --git a/src/backend/upgrade/ssh-terminal-macros-migration.ts b/src/backend/upgrade/ssh-terminal-macros-migration.ts deleted file mode 100644 index 933e7d1e3..000000000 --- a/src/backend/upgrade/ssh-terminal-macros-migration.ts +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Moves user_preferences.terminal_macros into the ssh-terminal plugin's - * macros user setting. Macros a browser kept on its own (storage mode - * "local") are moved by the plugin's frontend the first time the panel opens. - * - * The column keeps its value until 26.10.0 drops it. - * - * Idempotent: a user whose macros setting is already set is skipped. - */ - -import { sql } from "drizzle-orm"; -import { databaseLogger } from "../utils/logger.js"; -import { selectLegacyRows } from "../utils/crypto-migration/raw-rows.js"; -import { - createCurrentPluginRepository, - createCurrentPluginSettingsRepository, -} from "../database/repositories/factory.js"; - -const PLUGIN_ID = "ssh-terminal"; -const MAX_MACROS = 100; - -/** The saved macros in a 2.8 terminal_macros value, or null for none. */ -export function macrosFromPreferences(value: unknown): unknown[] | null { - if (typeof value !== "string" || !value) return null; - let parsed: unknown; - try { - parsed = JSON.parse(value); - } catch { - return null; - } - if (!Array.isArray(parsed)) return null; - const macros = parsed - .filter( - (macro) => - !!macro && - typeof macro === "object" && - typeof (macro as { id?: unknown }).id === "string" && - typeof (macro as { name?: unknown }).name === "string" && - Array.isArray((macro as { steps?: unknown }).steps), - ) - .slice(0, MAX_MACROS); - return macros.length > 0 ? macros : null; -} - -export async function runSshTerminalMacrosMigration(): Promise<{ - usersMoved: number; -}> { - const result = { usersMoved: 0 }; - try { - const plugin = await createCurrentPluginRepository().findById(PLUGIN_ID); - if (!plugin) return result; - } catch { - return result; - } - - const pluginSettings = createCurrentPluginSettingsRepository(); - try { - const rows = await selectLegacyRows<{ - user_id: string; - terminal_macros: unknown; - }>(sql` - SELECT user_id, terminal_macros FROM user_preferences - WHERE terminal_macros IS NOT NULL - `); - for (const row of rows) { - const macros = macrosFromPreferences(row.terminal_macros); - if (!macros) continue; - const existing = await pluginSettings.get( - PLUGIN_ID, - "user", - row.user_id, - "macros", - ); - if (existing && existing.value !== null) continue; - await pluginSettings.set( - PLUGIN_ID, - "user", - row.user_id, - "macros", - JSON.stringify(macros), - ); - result.usersMoved++; - } - } catch (error) { - databaseLogger.warn("Terminal macros migration failed", { - operation: "ssh_terminal_macros_migration", - error: error instanceof Error ? error.message : String(error), - }); - } - - if (result.usersMoved > 0) { - databaseLogger.info( - `Moved the terminal macros of ${result.usersMoved} user(s) into plugin settings`, - { operation: "ssh_terminal_macros_migration" }, - ); - } - return result; -} diff --git a/src/backend/upgrade/ssh-terminal-settings-migration.ts b/src/backend/upgrade/ssh-terminal-settings-migration.ts deleted file mode 100644 index 1c3fdb152..000000000 --- a/src/backend/upgrade/ssh-terminal-settings-migration.ts +++ /dev/null @@ -1,299 +0,0 @@ -/** - * Moves the terminal's settings out of core and into the ssh-terminal - * plugin's own settings. - * - * Admin keys from the core `settings` table: session persistence, command - * history, the terminal half of the admin host defaults (a new host's font, - * theme, cursor and auto tmux), touch input tuning and terminal image - * storage. The image storage - * local directory was never sent to a browser, so it lands as a secret field, - * which keeps it that way. The legacy rows are left in place for a release; - * nothing reads them any more. - * - * Host columns from ssh_data: enable_terminal, enable_terminal_toolbar and - * enable_command_history. All three default to on in the plugin, so only - * hosts that turned one off need a row. - * - * Idempotent: an admin key already set in the plugin, or a host that already - * has an enableTerminal row, is skipped, so this is safe on every boot. - */ - -import { sql } from "drizzle-orm"; -import { databaseLogger } from "../utils/logger.js"; -import { - legacyFlag, - selectLegacyRows, -} from "../utils/crypto-migration/raw-rows.js"; -import { - createCurrentPluginRepository, - createCurrentPluginSettingsRepository, - createCurrentSettingsRepository, -} from "../database/repositories/factory.js"; -import { encryptSystemSecret } from "../utils/system-secret-crypto.js"; - -const PLUGIN_ID = "ssh-terminal"; - -type Parse = (raw: string) => unknown; - -const asNumber: Parse = (raw) => { - const value = Number(raw); - return Number.isFinite(value) ? value : undefined; -}; -const asBoolean: Parse = (raw) => - raw === "true" || raw === "1" - ? true - : raw === "false" || raw === "0" - ? false - : undefined; -const asString: Parse = (raw) => raw; -const asJson: Parse = (raw) => { - try { - return JSON.parse(raw); - } catch { - return undefined; - } -}; - -const CURSOR_STYLES = ["block", "underline", "bar"]; -/** 2.8 spellings of the theme that follows the app. */ -const LEGACY_TERMIX_THEMES = [ - "Termix Dark", - "Termix Light", - "termixDark", - "termixLight", -]; - -/** One terminal key of the admin host defaults, or undefined when unusable. */ -function newHostDefault(key: string, defaults: unknown): unknown { - if (!defaults || typeof defaults !== "object") return undefined; - const value = (defaults as Record)[key]; - switch (key) { - case "fontSize": { - const size = Number(value); - return Number.isFinite(size) && size >= 8 && size <= 36 - ? size - : undefined; - } - case "fontFamily": - return typeof value === "string" && value ? value : undefined; - case "theme": - if (typeof value !== "string" || !value) return undefined; - return LEGACY_TERMIX_THEMES.includes(value) ? "termix" : value; - case "cursorStyle": - return typeof value === "string" && CURSOR_STYLES.includes(value) - ? value - : undefined; - default: - return typeof value === "boolean" ? value : undefined; - } -} - -/** Legacy core key -> the admin field the plugin declares. */ -const ADMIN_MOVES: { - legacyKey: string; - field: string; - parse: Parse; - secret?: boolean; -}[] = [ - { - legacyKey: "terminal_session_timeout_minutes", - field: "sessionTimeoutMinutes", - parse: asNumber, - }, - { - legacyKey: "terminal_session_persistence_enabled", - field: "sessionPersistence", - parse: asBoolean, - }, - { - legacyKey: "command_history_enabled", - field: "commandHistoryEnabled", - parse: asBoolean, - }, - { - // The admin host defaults carried the new-host command history switch. - legacyKey: "host_defaults", - field: "commandHistoryForNewHosts", - parse: (raw) => { - const value = (asJson(raw) as { enableCommandHistory?: unknown } | null) - ?.enableCommandHistory; - return typeof value === "boolean" ? value : undefined; - }, - }, - ...( - [ - ["fontSize", "newHostFontSize"], - ["fontFamily", "newHostFontFamily"], - ["theme", "newHostTheme"], - ["cursorStyle", "newHostCursorStyle"], - ["cursorBlink", "newHostCursorBlink"], - ["autoTmux", "newHostAutoTmux"], - ] as const - ).map(([legacyField, field]) => ({ - // The terminal half of the admin host defaults, for new hosts. - legacyKey: "host_defaults", - field, - parse: (raw: string) => newHostDefault(legacyField, asJson(raw)), - })), - { legacyKey: "touch_input_settings", field: "touchInput", parse: asJson }, - { - legacyKey: "terminal_image_storage_mode", - field: "imageStorageMode", - parse: asString, - }, - { - legacyKey: "terminal_image_local_dir", - field: "imageLocalDir", - parse: asString, - secret: true, - }, - { - legacyKey: "terminal_image_host_path", - field: "imageHostPath", - parse: asString, - }, - { legacyKey: "terminal_image_ttl_ms", field: "imageTtlMs", parse: asNumber }, - { - legacyKey: "terminal_image_max_count", - field: "imageMaxCount", - parse: asNumber, - }, - { - legacyKey: "terminal_image_max_storage_bytes", - field: "imageMaxBytes", - parse: asNumber, - }, -]; - -interface LegacyTerminalHostRow { - id: number; - enable_terminal: number | boolean | null; - enable_terminal_toolbar: number | boolean | null; - enable_command_history: number | boolean | null; -} - -/** A column that was never set keeps the old default, on. */ -function isOn(value: number | boolean | null): boolean { - return legacyFlag(value, true); -} - -export interface SshTerminalSettingsMigrationResult { - moved: string[]; - skipped: string[]; - hostsMoved: number; - hostsSkipped: number; -} - -export async function runSshTerminalSettingsMigration(): Promise { - const result: SshTerminalSettingsMigrationResult = { - moved: [], - skipped: [], - hostsMoved: 0, - hostsSkipped: 0, - }; - - try { - // plugin_settings has a foreign key to plugins: nothing to migrate into - // until the plugin row exists. - const plugin = await createCurrentPluginRepository().findById(PLUGIN_ID); - if (!plugin) return result; - - const settingsRepository = createCurrentSettingsRepository(); - const pluginSettingsRepository = createCurrentPluginSettingsRepository(); - - for (const move of ADMIN_MOVES) { - const legacyValue = await settingsRepository.get(move.legacyKey); - if (legacyValue === null || legacyValue === "") continue; - - const existing = await pluginSettingsRepository.get( - PLUGIN_ID, - "admin", - null, - move.field, - ); - if (existing && existing.value !== null) { - result.skipped.push(move.legacyKey); - continue; - } - - const parsed = move.parse(legacyValue); - if (parsed === undefined) { - result.skipped.push(move.legacyKey); - continue; - } - - const stored = move.secret - ? await encryptSystemSecret(String(parsed)) - : parsed; - await pluginSettingsRepository.set( - PLUGIN_ID, - "admin", - null, - move.field, - JSON.stringify(stored), - !!move.secret, - ); - result.moved.push(move.legacyKey); - } - - // Raw SQL, not the typed schema, so this keeps working once schema.ts - // stops declaring these columns. - const rows = await selectLegacyRows(sql` - SELECT id, enable_terminal, enable_terminal_toolbar, enable_command_history - FROM ssh_data - WHERE enable_terminal = false - OR enable_terminal_toolbar = false - OR enable_command_history = false - `); - - for (const row of rows) { - const scopeId = String(row.id); - const existing = await pluginSettingsRepository.get( - PLUGIN_ID, - "host", - scopeId, - "enableTerminal", - ); - if (existing && existing.value !== null) { - result.hostsSkipped++; - continue; - } - - const values: Record = { - enableTerminal: isOn(row.enable_terminal), - enableTerminalToolbar: isOn(row.enable_terminal_toolbar), - enableCommandHistory: isOn(row.enable_command_history), - }; - for (const [key, value] of Object.entries(values)) { - await pluginSettingsRepository.set( - PLUGIN_ID, - "host", - scopeId, - key, - JSON.stringify(value), - ); - } - result.hostsMoved++; - } - - if (result.moved.length > 0 || result.hostsMoved > 0) { - databaseLogger.info( - `Moved ${result.moved.length} terminal setting(s) and ${result.hostsMoved} host(s) into plugin settings`, - { - operation: "ssh_terminal_settings_migration", - moved: result.moved.join(", "), - hostsMoved: result.hostsMoved, - }, - ); - } - } catch (error) { - // A failed migration must not stop the backend. The terminal falls back - // to its defaults, which is a visible, recoverable state. - databaseLogger.warn("SSH terminal settings migration failed", { - operation: "ssh_terminal_settings_migration", - error: error instanceof Error ? error.message : String(error), - }); - } - - return result; -} diff --git a/src/backend/upgrade/sso-legacy-identity-migration.ts b/src/backend/upgrade/sso-legacy-identity-migration.ts index 93134d41f..975001b62 100644 --- a/src/backend/upgrade/sso-legacy-identity-migration.ts +++ b/src/backend/upgrade/sso-legacy-identity-migration.ts @@ -12,6 +12,9 @@ * and exactly one OIDC row is from before 2.9. An identity whose target is * already taken (a duplicate account made by 2.9.0) is left for an admin. * Idempotent. + * + * 2.9.1 ran it at boot. 26.10.0 runs it from the database setup, before + * users.oidc_identifier is dropped, for an install upgrading from 2.9.0. */ import { sql } from "drizzle-orm"; diff --git a/src/backend/upgrade/sso-settings-migration.ts b/src/backend/upgrade/sso-settings-migration.ts deleted file mode 100644 index 35b6c6b2e..000000000 --- a/src/backend/upgrade/sso-settings-migration.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Moves the silent sign-in default out of core settings and into the sso - * plugin's admin settings. Core used to own the toggle and read it on the - * login page; the sso plugin now marks its own login instance to start on - * its own. - * - * Idempotent: a value already in plugin settings wins and the core copy is - * dropped. - */ - -import { databaseLogger } from "../utils/logger.js"; -import { - createCurrentPluginRepository, - createCurrentPluginSettingsRepository, - createCurrentSettingsRepository, -} from "../database/repositories/factory.js"; - -const PLUGIN_ID = "sso"; -const LEGACY_KEY = "oidc_silent_login_default"; -const FIELD = "silentLoginDefault"; - -export interface SsoSettingsMigrationResult { - moved: boolean; -} - -export async function runSsoSettingsMigration(): Promise { - try { - const plugin = await createCurrentPluginRepository().findById(PLUGIN_ID); - if (!plugin) return { moved: false }; - - const settings = createCurrentSettingsRepository(); - const legacy = await settings.get(LEGACY_KEY); - if (legacy === null || legacy === "") return { moved: false }; - - const pluginSettings = createCurrentPluginSettingsRepository(); - const existing = await pluginSettings.get(PLUGIN_ID, "admin", null, FIELD); - if (!existing || existing.value === null) { - await pluginSettings.set( - PLUGIN_ID, - "admin", - null, - FIELD, - JSON.stringify(legacy === "true"), - false, - ); - } - await settings.delete(LEGACY_KEY); - databaseLogger.info("Moved the silent sign-in default into sso settings", { - operation: "sso_settings_migration", - }); - return { moved: true }; - } catch (error) { - databaseLogger.warn("SSO settings migration failed", { - operation: "sso_settings_migration", - error: error instanceof Error ? error.message : String(error), - }); - return { moved: false }; - } -} diff --git a/src/backend/upgrade/step-ca-settings-migration.ts b/src/backend/upgrade/step-ca-settings-migration.ts deleted file mode 100644 index 9dfc3cd70..000000000 --- a/src/backend/upgrade/step-ca-settings-migration.ts +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Moves the 2.8 Step CA configuration into the step-ca plugin. - * - * The CA URL, root fingerprint, OIDC provisioner and private endpoint - * allowlist lived in core `settings` rows. They become the plugin's admin - * settings. An install that had a CA configured keeps sending the pre-2.9 - * redirect URI (/host/step-ca-callback), because that is the one registered - * with its identity provider: the plugin's legacyCallback setting is turned - * on. - * - * Idempotent: a value already present in plugin_settings is never - * overwritten. The old rows stay for one release so a downgrade still works. - */ - -import { databaseLogger } from "../utils/logger.js"; -import { - createCurrentPluginRepository, - createCurrentPluginSettingsRepository, - createCurrentSettingsRepository, -} from "../database/repositories/factory.js"; -import { parseNotificationAllowlist } from "../utils/notification-egress.js"; - -const PLUGIN_ID = "step-ca"; - -const MOVES: { legacyKey: string; field: string }[] = [ - { legacyKey: "step_ca_url", field: "caUrl" }, - { legacyKey: "step_ca_fingerprint", field: "fingerprint" }, - { legacyKey: "step_ca_provisioner", field: "provisioner" }, -]; - -const LEGACY_ALLOWLIST_KEY = "step_ca_private_endpoint_allowlist"; - -export interface StepCaSettingsMigrationResult { - moved: string[]; - legacyCallback: boolean; -} - -export async function runStepCaSettingsMigration(): Promise { - const result: StepCaSettingsMigrationResult = { - moved: [], - legacyCallback: false, - }; - - try { - // plugin_settings has a foreign key to plugins: nothing to move into yet. - const plugin = await createCurrentPluginRepository().findById(PLUGIN_ID); - if (!plugin) return result; - - const settings = createCurrentSettingsRepository(); - const pluginSettings = createCurrentPluginSettingsRepository(); - - const writeIfUnset = async (field: string, value: unknown) => { - const existing = await pluginSettings.get( - PLUGIN_ID, - "admin", - null, - field, - ); - if (existing && existing.value !== null) return false; - await pluginSettings.set( - PLUGIN_ID, - "admin", - null, - field, - JSON.stringify(value), - ); - return true; - }; - - for (const move of MOVES) { - const value = (await settings.get(move.legacyKey))?.trim(); - if (!value) continue; - if (await writeIfUnset(move.field, value)) { - result.moved.push(move.legacyKey); - } - } - - const hosts = parseNotificationAllowlist( - await settings.get(LEGACY_ALLOWLIST_KEY), - ); - if ( - hosts.length > 0 && - (await writeIfUnset("privateEndpoints", hosts.join(", "))) - ) { - result.moved.push(LEGACY_ALLOWLIST_KEY); - } - - if ((await settings.get("step_ca_url"))?.trim()) { - result.legacyCallback = await writeIfUnset("legacyCallback", true); - } - - if (result.moved.length > 0) { - databaseLogger.info( - `Moved ${result.moved.length} Step CA setting(s) into plugin settings`, - { - operation: "step_ca_settings_migration", - moved: result.moved.join(", "), - }, - ); - } - } catch (error) { - // Must not stop the backend: the plugin then reports "not configured". - databaseLogger.warn("Step CA settings migration failed", { - operation: "step_ca_settings_migration", - error: error instanceof Error ? error.message : String(error), - }); - } - - return result; -} diff --git a/src/backend/upgrade/tailscale-settings-migration.ts b/src/backend/upgrade/tailscale-settings-migration.ts deleted file mode 100644 index 141135eef..000000000 --- a/src/backend/upgrade/tailscale-settings-migration.ts +++ /dev/null @@ -1,105 +0,0 @@ -/** - * Moves the Tailscale API key and base URL out of core settings. - * - * Both lived in the core `settings` table, written by a core route that existed - * only to serve one plugin. A6 gives plugins their own settings, so they belong - * in plugin_settings under the tailscale plugin. - * - * The key was stored in plaintext, and the route that read it back masked only - * its tail while being open to any authenticated user. Landing it as a secret - * field encrypts it at rest, so this migration is a fix as much as a move. - * - * Idempotent in two ways: it stops when a value is already present in the new - * home, and encryptSystemSecret returns an already-encrypted value unchanged, - * so a second pass can neither duplicate nor double-encrypt. - */ - -import { databaseLogger } from "../utils/logger.js"; -import { - createCurrentPluginRepository, - createCurrentPluginSettingsRepository, - createCurrentSettingsRepository, -} from "../database/repositories/factory.js"; -import { encryptSystemSecret } from "../utils/system-secret-crypto.js"; - -const PLUGIN_ID = "tailscale"; - -/** Legacy core key -> the field the plugin now declares. */ -const MOVES: { legacyKey: string; field: string; secret: boolean }[] = [ - { legacyKey: "tailscale_api_key", field: "apiKey", secret: true }, - { legacyKey: "tailscale_api_base_url", field: "apiBaseUrl", secret: false }, -]; - -export interface TailscaleSettingsMigrationResult { - moved: string[]; - skipped: string[]; -} - -export async function runTailscaleSettingsMigration(): Promise { - const moved: string[] = []; - const skipped: string[] = []; - - try { - // The FK to plugins means there is nothing to migrate into until the - // plugin has been seeded. Not an error: a build without it just has no - // tailscale rows. - const plugin = await createCurrentPluginRepository().findById(PLUGIN_ID); - if (!plugin) return { moved, skipped }; - - const settingsRepository = createCurrentSettingsRepository(); - const pluginSettingsRepository = createCurrentPluginSettingsRepository(); - - for (const move of MOVES) { - const legacyValue = await settingsRepository.get(move.legacyKey); - if (legacyValue === null || legacyValue === "") { - continue; - } - - const existing = await pluginSettingsRepository.get( - PLUGIN_ID, - "admin", - null, - move.field, - ); - if (existing && existing.value !== null) { - // Already moved. Drop the stale copy rather than leaving a plaintext - // key behind for a reader that predates this change. - await settingsRepository.delete(move.legacyKey); - skipped.push(move.legacyKey); - continue; - } - - const stored = move.secret - ? await encryptSystemSecret(legacyValue) - : legacyValue; - - await pluginSettingsRepository.set( - PLUGIN_ID, - "admin", - null, - move.field, - JSON.stringify(stored), - move.secret, - ); - - await settingsRepository.delete(move.legacyKey); - moved.push(move.legacyKey); - } - - if (moved.length > 0) { - databaseLogger.info( - `Moved ${moved.length} Tailscale setting(s) into plugin settings`, - { operation: "tailscale_settings_migration", moved: moved.join(", ") }, - ); - } - } catch (error) { - // A failed migration must not stop the backend. The plugin falls back to - // "no key configured", which is a visible, recoverable state. - databaseLogger.warn("Tailscale settings migration failed", { - operation: "tailscale_settings_migration", - error: error instanceof Error ? error.message : String(error), - }); - } - - return { moved, skipped }; -} diff --git a/src/backend/upgrade/telemetry-settings-migration.ts b/src/backend/upgrade/telemetry-settings-migration.ts deleted file mode 100644 index c35c02459..000000000 --- a/src/backend/upgrade/telemetry-settings-migration.ts +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Moves the 2.8 telemetry settings into the telemetry plugin: the admin - * switch (settings.analytics_enabled) into its `enabled` admin setting, and - * the instance id (settings.analytics_instance_id) into `instanceId`, so the - * instance keeps reporting under the same id. - * - * Idempotent: nothing is written where the plugin already has a row. The - * legacy settings rows stay in place until 26.10.0. The plugin waits a few - * minutes before its first send, so this lands first. - */ - -import { databaseLogger } from "../utils/logger.js"; -import { - createCurrentPluginRepository, - createCurrentPluginSettingsRepository, - createCurrentSettingsRepository, -} from "../database/repositories/factory.js"; - -const PLUGIN_ID = "telemetry"; - -export interface TelemetrySettingsMigrationResult { - movedEnabled: boolean; - movedInstanceId: boolean; -} - -export async function runTelemetrySettingsMigration(): Promise { - const result: TelemetrySettingsMigrationResult = { - movedEnabled: false, - movedInstanceId: false, - }; - - // plugin_settings has a foreign key to plugins. - try { - if (!(await createCurrentPluginRepository().findById(PLUGIN_ID))) { - return result; - } - } catch { - return result; - } - - const coreSettings = createCurrentSettingsRepository(); - const pluginSettings = createCurrentPluginSettingsRepository(); - - const moves: Array<[string, string, unknown]> = []; - if ((await coreSettings.get("analytics_enabled")) === "false") { - moves.push(["analytics_enabled", "enabled", false]); - } - const instanceId = await coreSettings.get("analytics_instance_id"); - if (instanceId) { - moves.push(["analytics_instance_id", "instanceId", instanceId]); - } - - for (const [, key, value] of moves) { - const existing = await pluginSettings.get(PLUGIN_ID, "admin", null, key); - if (existing && existing.value !== null) continue; - await pluginSettings.set( - PLUGIN_ID, - "admin", - null, - key, - JSON.stringify(value), - ); - if (key === "enabled") result.movedEnabled = true; - else result.movedInstanceId = true; - } - - if (result.movedEnabled || result.movedInstanceId) { - databaseLogger.info("Moved telemetry settings into plugin settings", { - operation: "telemetry_settings_migration", - ...result, - }); - } - return result; -} diff --git a/src/backend/upgrade/tmux-monitor-settings-migration.ts b/src/backend/upgrade/tmux-monitor-settings-migration.ts deleted file mode 100644 index 8ada890f5..000000000 --- a/src/backend/upgrade/tmux-monitor-settings-migration.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Moves the tmux monitor host column into the tmux-monitor plugin's host-scope - * settings, before the column is dropped from ssh_data. - * - * Idempotent: skips a host that already has a tmux-monitor settings row, so it - * is safe to run on every boot. Lossless: reads straight off the still-present - * ssh_data column, so it must run and finish before that column is ever - * dropped from schema.ts. - */ - -import { sql } from "drizzle-orm"; -import { databaseLogger } from "../utils/logger.js"; -import { selectLegacyRows } from "../utils/crypto-migration/raw-rows.js"; -import { createCurrentPluginSettingsRepository } from "../database/repositories/factory.js"; - -export interface TmuxMonitorSettingsMigrationResult { - moved: number; - skipped: number; -} - -interface LegacyTmuxMonitorRow { - id: number; - enable_tmux_monitor: number | boolean | null; -} - -export async function runTmuxMonitorSettingsMigration(): Promise { - const result: TmuxMonitorSettingsMigrationResult = { moved: 0, skipped: 0 }; - - try { - // Raw SQL, not the typed schema, so this keeps working once schema.ts - // stops declaring this column. The plugin's enable switch defaults to - // off, matching the old column's default, so only hosts that turned it - // on need a row. - const rows = await selectLegacyRows(sql` - SELECT id, enable_tmux_monitor - FROM ssh_data - WHERE enable_tmux_monitor = true - `); - - if (rows.length === 0) return result; - - const pluginSettingsRepository = createCurrentPluginSettingsRepository(); - - for (const row of rows) { - const scopeId = String(row.id); - const existing = await pluginSettingsRepository.get( - "tmux-monitor", - "host", - scopeId, - "enableTmuxMonitor", - ); - if (existing && existing.value !== null) { - result.skipped++; - continue; - } - - await pluginSettingsRepository.set( - "tmux-monitor", - "host", - scopeId, - "enableTmuxMonitor", - JSON.stringify(!!row.enable_tmux_monitor), - ); - result.moved++; - } - - if (result.moved > 0) { - databaseLogger.info( - `Moved tmux monitor settings for ${result.moved} host(s) into plugin settings`, - { operation: "tmux_monitor_settings_migration", moved: result.moved }, - ); - } - } catch (error) { - // A failed migration must not stop the backend. Hosts simply keep the - // monitor off, the plugin's default. - databaseLogger.warn("Tmux monitor settings migration failed", { - operation: "tmux_monitor_settings_migration", - error: error instanceof Error ? error.message : String(error), - }); - } - - return result; -} diff --git a/src/backend/upgrade/tunnels-settings-migration.ts b/src/backend/upgrade/tunnels-settings-migration.ts deleted file mode 100644 index b99ec50f1..000000000 --- a/src/backend/upgrade/tunnels-settings-migration.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Moves the two tunnel host columns into the tunnels plugin's host-scope - * settings, before those columns are dropped from ssh_data. - * - * Idempotent: skips a host that already has a tunnels settings row, so it is - * safe to run on every boot. Lossless: reads straight off the still-present - * ssh_data columns, so it must run and finish before those columns are ever - * dropped from schema.ts. - */ - -import { sql } from "drizzle-orm"; -import { databaseLogger } from "../utils/logger.js"; -import { selectLegacyRows } from "../utils/crypto-migration/raw-rows.js"; -import { createCurrentPluginSettingsRepository } from "../database/repositories/factory.js"; - -export interface TunnelsSettingsMigrationResult { - moved: number; - skipped: number; -} - -interface LegacyTunnelRow { - id: number; - enable_tunnel: number | boolean | null; - tunnel_connections: string | null; -} - -function parseConnections(raw: string | null): unknown[] { - if (!raw) return []; - try { - const parsed = JSON.parse(raw); - return Array.isArray(parsed) ? parsed : []; - } catch { - return []; - } -} - -export async function runTunnelsSettingsMigration(): Promise { - const result: TunnelsSettingsMigrationResult = { moved: 0, skipped: 0 }; - - try { - // Raw SQL, not the typed schema, so this keeps working once schema.ts - // stops declaring these columns. The plugin's enable switch defaults to - // off, so every host that had tunnels on (the old column default) or - // any saved tunnel has to be copied, not just the non-default ones. - const rows = await selectLegacyRows(sql` - SELECT id, enable_tunnel, tunnel_connections - FROM ssh_data - WHERE enable_tunnel = true OR tunnel_connections IS NOT NULL - `); - - if (rows.length === 0) return result; - - const pluginSettingsRepository = createCurrentPluginSettingsRepository(); - - for (const row of rows) { - const scopeId = String(row.id); - const existing = await pluginSettingsRepository.get( - "tunnels", - "host", - scopeId, - "enableTunnel", - ); - if (existing && existing.value !== null) { - result.skipped++; - continue; - } - - await pluginSettingsRepository.set( - "tunnels", - "host", - scopeId, - "enableTunnel", - JSON.stringify(!!row.enable_tunnel), - ); - await pluginSettingsRepository.set( - "tunnels", - "host", - scopeId, - "tunnelConnections", - JSON.stringify(parseConnections(row.tunnel_connections)), - ); - result.moved++; - } - - if (result.moved > 0) { - databaseLogger.info( - `Moved tunnel settings for ${result.moved} host(s) into plugin settings`, - { operation: "tunnels_settings_migration", moved: result.moved }, - ); - } - } catch (error) { - // A failed migration must not stop the backend. Hosts simply keep - // whatever tunnel settings they already had (none, on a fresh run). - databaseLogger.warn("Tunnel settings migration failed", { - operation: "tunnels_settings_migration", - error: error instanceof Error ? error.message : String(error), - }); - } - - return result; -} diff --git a/src/backend/upgrade/vault-settings-migration.ts b/src/backend/upgrade/vault-settings-migration.ts deleted file mode 100644 index 22ea4e9f3..000000000 --- a/src/backend/upgrade/vault-settings-migration.ts +++ /dev/null @@ -1,149 +0,0 @@ -/** - * Moves each host's Vault signer profile out of ssh_data and into the vault - * plugin's profileId host setting. - * - * An install that used Vault keeps sending the pre-2.9 redirect URI - * (/vault/oidc/callback), because that is the one its Vault OIDC roles - * allow: the plugin's legacyCallback setting is turned on. - * - * The column stays in the database, unused: core's drizzle migrations run - * before this does, so dropping it there would lose the data first. - * - * Idempotent: a host or setting that already has a value is skipped, so this - * is safe on every boot. - */ - -import { sql } from "drizzle-orm"; -import { databaseLogger } from "../utils/logger.js"; -import { - createCurrentPluginRepository, - createCurrentPluginSettingsRepository, -} from "../database/repositories/factory.js"; -import { selectRows } from "../utils/crypto-migration/raw-rows.js"; - -const PLUGIN_ID = "vault"; -const SETTING_KEY = "profileId"; - -interface LegacyHostRow { - id: number; - vault_profile_id: number | string | null; -} - -export interface VaultSettingsMigrationResult { - hostsMoved: number; - hostsSkipped: number; - legacyCallback: boolean; -} - -async function legacyRows(): Promise { - try { - // Raw SQL: schema.ts no longer declares vault_profile_id. - return await selectRows( - sql`SELECT id, vault_profile_id FROM ssh_data WHERE vault_profile_id IS NOT NULL`, - ); - } catch { - // A fresh install never had the column. - return []; - } -} - -/** Whether 2.8 had any Vault profile, before or after the plugin adopted them. */ -async function hadProfiles(): Promise { - for (const table of ["vault_profiles", "p_vault_profiles"]) { - try { - const rows = await selectRows( - sql`SELECT 1 FROM ${sql.identifier(table)} LIMIT 1`, - ); - if (rows.length > 0) return true; - } catch { - // table not there - } - } - return false; -} - -export async function runVaultSettingsMigration(): Promise { - const result: VaultSettingsMigrationResult = { - hostsMoved: 0, - hostsSkipped: 0, - legacyCallback: false, - }; - - // plugin_settings has a foreign key to plugins: nothing to migrate into - // until the plugin row exists. - try { - const plugin = await createCurrentPluginRepository().findById(PLUGIN_ID); - if (!plugin) return result; - } catch { - return result; - } - - const pluginSettings = createCurrentPluginSettingsRepository(); - const rows = await legacyRows(); - - for (const row of rows) { - const profileId = Number(row.vault_profile_id); - if (!Number.isInteger(profileId) || profileId <= 0) continue; - const scopeId = String(row.id); - try { - const existing = await pluginSettings.get( - PLUGIN_ID, - "host", - scopeId, - SETTING_KEY, - ); - if (existing) { - result.hostsSkipped++; - continue; - } - await pluginSettings.set( - PLUGIN_ID, - "host", - scopeId, - SETTING_KEY, - JSON.stringify(profileId), - ); - result.hostsMoved++; - } catch (error) { - databaseLogger.warn("Vault host settings migration failed for a host", { - operation: "vault_settings_migration", - hostId: row.id, - error: error instanceof Error ? error.message : String(error), - }); - } - } - - // Decided once, on the first boot with the plugin: written either way, so - // profiles created on a fresh install later never turn it on. - try { - const existing = await pluginSettings.get( - PLUGIN_ID, - "admin", - null, - "legacyCallback", - ); - if (!existing || existing.value === null) { - result.legacyCallback = rows.length > 0 || (await hadProfiles()); - await pluginSettings.set( - PLUGIN_ID, - "admin", - null, - "legacyCallback", - JSON.stringify(result.legacyCallback), - ); - } - } catch (error) { - databaseLogger.warn("Vault legacy callback migration failed", { - operation: "vault_settings_migration", - error: error instanceof Error ? error.message : String(error), - }); - } - - if (result.hostsMoved > 0) { - databaseLogger.info( - `Moved the Vault profile for ${result.hostsMoved} host(s) into plugin settings`, - { operation: "vault_settings_migration" }, - ); - } - return result; -} diff --git a/src/backend/upgrade/wake-on-lan-settings-migration.ts b/src/backend/upgrade/wake-on-lan-settings-migration.ts deleted file mode 100644 index 9a5e7b479..000000000 --- a/src/backend/upgrade/wake-on-lan-settings-migration.ts +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Moves each host's Wake-on-LAN settings out of ssh_data and into the - * wake-on-lan plugin's host settings: mac_address becomes macAddress and - * wol_broadcast_address becomes broadcastAddress. - * - * The columns stay in the database, unused: core's drizzle migrations run - * before this does, so dropping them there would lose the data first. - * - * Idempotent: a host that already has any wake-on-lan row is skipped, so - * this is safe on every boot. - */ - -import { sql } from "drizzle-orm"; -import { databaseLogger } from "../utils/logger.js"; -import { - createCurrentPluginRepository, - createCurrentPluginSettingsRepository, -} from "../database/repositories/factory.js"; -import { selectRows } from "../utils/crypto-migration/raw-rows.js"; - -const PLUGIN_ID = "wake-on-lan"; - -interface LegacyHostRow { - id: number; - mac_address: string | null; - wol_broadcast_address: string | null; -} - -/** The plugin's host settings for one legacy row, defaults left out. */ -export function wakeOnLanSettingsFromRow( - row: LegacyHostRow, -): Record { - const values: Record = {}; - if (row.mac_address) values.macAddress = row.mac_address; - if (row.wol_broadcast_address) { - values.broadcastAddress = row.wol_broadcast_address; - } - return values; -} - -export interface WakeOnLanSettingsMigrationResult { - hostsMoved: number; - hostsSkipped: number; -} - -export async function runWakeOnLanSettingsMigration(): Promise { - const result: WakeOnLanSettingsMigrationResult = { - hostsMoved: 0, - hostsSkipped: 0, - }; - - // plugin_settings has a foreign key to plugins: nothing to migrate into - // until the plugin row exists. - try { - const plugin = await createCurrentPluginRepository().findById(PLUGIN_ID); - if (!plugin) return result; - } catch { - return result; - } - - const pluginSettings = createCurrentPluginSettingsRepository(); - - let rows: LegacyHostRow[]; - try { - // Raw SQL: schema.ts no longer declares these columns. - rows = await selectRows( - sql`SELECT id, mac_address, wol_broadcast_address FROM ssh_data`, - ); - } catch { - // A fresh install never had the columns. - return result; - } - - for (const row of rows) { - const values = wakeOnLanSettingsFromRow(row); - if (Object.keys(values).length === 0) continue; - const scopeId = String(row.id); - try { - const existing = await pluginSettings.getAll(PLUGIN_ID, "host", scopeId); - if (existing.length > 0) { - result.hostsSkipped++; - continue; - } - for (const [key, value] of Object.entries(values)) { - await pluginSettings.set( - PLUGIN_ID, - "host", - scopeId, - key, - JSON.stringify(value), - ); - } - result.hostsMoved++; - } catch (error) { - databaseLogger.warn( - "Wake-on-LAN host settings migration failed for a host", - { - operation: "wake_on_lan_settings_migration", - hostId: row.id, - error: error instanceof Error ? error.message : String(error), - }, - ); - } - } - - if (result.hostsMoved > 0) { - databaseLogger.info( - `Moved Wake-on-LAN settings for ${result.hostsMoved} host(s) into plugin settings`, - { operation: "wake_on_lan_settings_migration" }, - ); - } - return result; -} diff --git a/src/backend/upgrade/warpgate-settings-migration.ts b/src/backend/upgrade/warpgate-settings-migration.ts deleted file mode 100644 index 81f6a4165..000000000 --- a/src/backend/upgrade/warpgate-settings-migration.ts +++ /dev/null @@ -1,134 +0,0 @@ -/** - * Moves each host's Warpgate flag out of ssh_data and into the warpgate - * plugin's useWarpgate host setting. Hosts from before 2.7 that still carry - * auth_type "warpgate" get the setting too, and auth type "none", which is - * what that auth type meant. - * - * The column stays in the database, unused: core's drizzle migrations run - * before this does, so dropping it there would lose the data first. - * - * Idempotent: a host that already has the setting is skipped, so this is - * safe on every boot. - */ - -import { sql } from "drizzle-orm"; -import { databaseLogger } from "../utils/logger.js"; -import { - createCurrentPluginRepository, - createCurrentPluginSettingsRepository, -} from "../database/repositories/factory.js"; -import { - runStatement, - selectRows, -} from "../utils/crypto-migration/raw-rows.js"; - -const PLUGIN_ID = "warpgate"; -const SETTING_KEY = "useWarpgate"; - -interface LegacyHostRow { - id: number; - use_warpgate?: number | boolean | string | null; - auth_type: string | null; -} - -export function usesWarpgate(row: LegacyHostRow): boolean { - const flag = row.use_warpgate; - return ( - flag === true || - flag === 1 || - flag === "1" || - flag === "true" || - row.auth_type === "warpgate" - ); -} - -export interface WarpgateSettingsMigrationResult { - hostsMoved: number; - hostsSkipped: number; - authTypesFixed: number; -} - -async function legacyRows(): Promise { - try { - // Raw SQL: schema.ts no longer declares use_warpgate. - return await selectRows( - sql`SELECT id, use_warpgate, auth_type FROM ssh_data`, - ); - } catch { - // A fresh install never had the column. - try { - return await selectRows( - sql`SELECT id, auth_type FROM ssh_data WHERE auth_type = 'warpgate'`, - ); - } catch { - return []; - } - } -} - -export async function runWarpgateSettingsMigration(): Promise { - const result: WarpgateSettingsMigrationResult = { - hostsMoved: 0, - hostsSkipped: 0, - authTypesFixed: 0, - }; - - // plugin_settings has a foreign key to plugins: nothing to migrate into - // until the plugin row exists. - try { - const plugin = await createCurrentPluginRepository().findById(PLUGIN_ID); - if (!plugin) return result; - } catch { - return result; - } - - const pluginSettings = createCurrentPluginSettingsRepository(); - - for (const row of await legacyRows()) { - if (!usesWarpgate(row)) continue; - const scopeId = String(row.id); - try { - const existing = await pluginSettings.get( - PLUGIN_ID, - "host", - scopeId, - SETTING_KEY, - ); - if (existing) { - result.hostsSkipped++; - } else { - await pluginSettings.set( - PLUGIN_ID, - "host", - scopeId, - SETTING_KEY, - JSON.stringify(true), - ); - result.hostsMoved++; - } - if (row.auth_type === "warpgate") { - await runStatement( - sql`UPDATE ssh_data SET auth_type = 'none' WHERE id = ${row.id}`, - ); - result.authTypesFixed++; - } - } catch (error) { - databaseLogger.warn( - "Warpgate host settings migration failed for a host", - { - operation: "warpgate_settings_migration", - hostId: row.id, - error: error instanceof Error ? error.message : String(error), - }, - ); - } - } - - if (result.hostsMoved > 0) { - databaseLogger.info( - `Moved the Warpgate flag for ${result.hostsMoved} host(s) into plugin settings`, - { operation: "warpgate_settings_migration" }, - ); - } - return result; -} diff --git a/src/backend/upgrade/web-endpoint-settings-migration.ts b/src/backend/upgrade/web-endpoint-settings-migration.ts deleted file mode 100644 index f80d1dba7..000000000 --- a/src/backend/upgrade/web-endpoint-settings-migration.ts +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Moves the two web endpoint host columns into the web-endpoint plugin's - * host-scope settings, before those columns are dropped from ssh_data. - * - * Idempotent: skips a host that already has a web-endpoint settings row, so - * it is safe to run on every boot. Lossless: reads straight off the - * still-present ssh_data columns, so it must run and finish before those - * columns are ever dropped from schema.ts. - */ - -import { sql } from "drizzle-orm"; -import { databaseLogger } from "../utils/logger.js"; -import { selectLegacyRows } from "../utils/crypto-migration/raw-rows.js"; -import { createCurrentPluginSettingsRepository } from "../database/repositories/factory.js"; - -export interface WebEndpointSettingsMigrationResult { - moved: number; - skipped: number; -} - -interface LegacyWebEndpointRow { - id: number; - enable_web_ui: number | boolean | null; - web_ui_config: string | null; -} - -function parseConfig(raw: string | null): unknown { - if (!raw) return null; - try { - return JSON.parse(raw); - } catch { - return null; - } -} - -export async function runWebEndpointSettingsMigration(): Promise { - const result: WebEndpointSettingsMigrationResult = { moved: 0, skipped: 0 }; - - try { - // Raw SQL, not the typed schema, so this keeps working once schema.ts - // stops declaring these columns. - const rows = await selectLegacyRows(sql` - SELECT id, enable_web_ui, web_ui_config - FROM ssh_data - WHERE enable_web_ui = true OR web_ui_config IS NOT NULL - `); - - if (rows.length === 0) return result; - - const pluginSettingsRepository = createCurrentPluginSettingsRepository(); - - for (const row of rows) { - const scopeId = String(row.id); - const existing = await pluginSettingsRepository.get( - "web-endpoint", - "host", - scopeId, - "enableWebUi", - ); - if (existing && existing.value !== null) { - result.skipped++; - continue; - } - - await pluginSettingsRepository.set( - "web-endpoint", - "host", - scopeId, - "enableWebUi", - JSON.stringify(!!row.enable_web_ui), - ); - await pluginSettingsRepository.set( - "web-endpoint", - "host", - scopeId, - "webUiConfig", - JSON.stringify(parseConfig(row.web_ui_config)), - ); - result.moved++; - } - - if (result.moved > 0) { - databaseLogger.info( - `Moved web endpoint settings for ${result.moved} host(s) into plugin settings`, - { operation: "web_endpoint_settings_migration", moved: result.moved }, - ); - } - } catch (error) { - // A failed migration must not stop the backend. Hosts simply keep - // whatever web endpoint settings they already had (none, on a fresh run). - databaseLogger.warn("Web endpoint settings migration failed", { - operation: "web_endpoint_settings_migration", - error: error instanceof Error ? error.message : String(error), - }); - } - - return result; -} diff --git a/src/backend/utils/auth-manager.ts b/src/backend/utils/auth-manager.ts index ef564da60..d7a330c7c 100644 --- a/src/backend/utils/auth-manager.ts +++ b/src/backend/utils/auth-manager.ts @@ -13,7 +13,8 @@ import { logAudit, getRequestMeta } from "./audit-logger.js"; import type { Request, Response, NextFunction } from "express"; import bcrypt from "bcryptjs"; import { nanoid } from "nanoid"; -import { and, eq, inArray } from "drizzle-orm"; +import { inArray, isNotNull } from "drizzle-orm"; +import type { ExternalSessionRef } from "../auth/types.js"; import type { DeviceType } from "./user-agent-parser.js"; import { getDb } from "../database/db/index.js"; import { sessions } from "../database/db/schema.js"; @@ -68,6 +69,29 @@ interface RequestWithHeaders extends Request { }; } +function serializeExternalSession( + ref: ExternalSessionRef | null | undefined, +): string | null { + if (!ref || (!ref.sub && !ref.sid)) return null; + return JSON.stringify({ + providerId: ref.providerId ?? null, + sub: ref.sub ?? null, + sid: ref.sid ?? null, + }); +} + +function parseExternalSession( + value: string | null | undefined, +): ExternalSessionRef | null { + if (!value) return null; + try { + const parsed = JSON.parse(value) as ExternalSessionRef; + return parsed && typeof parsed === "object" ? parsed : null; + } catch { + return null; + } +} + const ADMIN_TARGET_USER_HEADER = "x-admin-target-user"; // Core data-plane routes an admin may hit on behalf of another user. @@ -283,9 +307,7 @@ class AuthManager { rememberMe?: boolean; deviceType?: DeviceType; deviceInfo?: string; - oidcSub?: string | null; - oidcSid?: string | null; - ssoProviderId?: number | null; + externalSession?: ExternalSessionRef | null; } = {}, ): Promise { const jwtSecret = await this.systemCrypto.getJWTSecret(); @@ -331,9 +353,7 @@ class AuthManager { jwtToken: token, deviceType: options.deviceType, deviceInfo: options.deviceInfo, - oidcSub: options.oidcSub ?? null, - oidcSid: options.oidcSid ?? null, - ssoProviderId: options.ssoProviderId ?? null, + externalSessionRef: serializeExternalSession(options.externalSession), createdAt, expiresAt, lastActiveAt: createdAt, @@ -506,35 +526,36 @@ class AuthManager { } async revokeSessionsByExternalSession(params: { - ssoProviderId?: number | null; + providerId?: number | null; sub?: string | null; sid?: string | null; }): Promise { - const { ssoProviderId, sub, sid } = params; + const { providerId, sub, sid } = params; if (!sub && !sid) return 0; try { - const conditions = [ - sid ? eq(sessions.oidcSid, sid) : eq(sessions.oidcSub, sub!), - ]; - if (ssoProviderId != null) - conditions.push(eq(sessions.ssoProviderId, ssoProviderId)); - const db = getDb(); - const matched = await db - .select() + const candidates = await db + .select({ id: sessions.id, ref: sessions.externalSessionRef }) .from(sessions) - .where(conditions.length === 1 ? conditions[0] : and(...conditions)); + .where(isNotNull(sessions.externalSessionRef)); - if (matched.length === 0) return 0; + const matchedIds = candidates + .filter((row) => { + const ref = parseExternalSession(row.ref); + if (!ref) return false; + if (sid ? ref.sid !== sid : ref.sub !== sub) return false; + return providerId == null || ref.providerId === providerId; + }) + .map((row) => row.id); - const matchedIds = matched.map((s) => s.id); + if (matchedIds.length === 0) return 0; await db.delete(sessions).where(inArray(sessions.id, matchedIds)); - authLogger.info("Sessions revoked via OIDC back-channel logout", { - operation: "oidc_backchannel_logout", - ssoProviderId, + authLogger.info("Sessions revoked via back-channel logout", { + operation: "external_backchannel_logout", + providerId, sessionCount: matchedIds.length, }); @@ -544,9 +565,13 @@ class AuthManager { return matchedIds.length; } catch (error) { - databaseLogger.error("Failed to revoke sessions via OIDC", error, { - operation: "oidc_backchannel_logout_failed", - }); + databaseLogger.error( + "Failed to revoke sessions via back-channel logout", + error, + { + operation: "external_backchannel_logout_failed", + }, + ); throw error; } } diff --git a/src/backend/utils/crypto-migration/host-status-config-migration.ts b/src/backend/utils/crypto-migration/host-status-config-migration.ts deleted file mode 100644 index 1d900bc50..000000000 --- a/src/backend/utils/crypto-migration/host-status-config-migration.ts +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Copies the status check options out of ssh_data.stats_config into the - * status_check_enabled and status_check_interval columns. Status checks are - * core; the rest of stats_config belongs to the host-metrics plugin and is - * moved by host-metrics-settings-migration.ts. - * - * The stats_config column stays in the database, unused: core's drizzle - * migrations run before this does, so dropping it there would lose the data. - * - * Runs once, marked by a settings row, because the new columns have defaults - * and there is no other way to tell a copied row from an untouched one. - */ - -import { sql } from "drizzle-orm"; -import { databaseLogger } from "../logger.js"; -import { createCurrentSettingsRepository } from "../../database/repositories/factory.js"; -import { runStatement, selectRows } from "./raw-rows.js"; - -export const HOST_STATUS_CONFIG_MIGRATED = "host_status_config_migrated"; - -export interface LegacyStatusConfig { - statusCheckEnabled: boolean; - statusCheckInterval: number | null; -} - -/** The status part of an old stats_config value. */ -export function statusConfigFromStatsConfig( - raw: string | null, -): LegacyStatusConfig { - let parsed: unknown = null; - try { - parsed = raw ? JSON.parse(raw) : null; - // Some rows were stringified twice. - if (typeof parsed === "string") parsed = JSON.parse(parsed); - } catch { - parsed = null; - } - const config = - parsed && typeof parsed === "object" - ? (parsed as Record) - : {}; - const seconds = Number(config.statusCheckInterval); - return { - statusCheckEnabled: - config.statusCheckEnabled !== false && config.disableTcpPing !== true, - statusCheckInterval: - config.useGlobalStatusInterval === false && - Number.isInteger(seconds) && - seconds >= 5 - ? seconds - : null, - }; -} - -export interface HostStatusConfigMigrationResult { - skipped: boolean; - hostsMoved: number; -} - -export async function runHostStatusConfigMigration(): Promise { - const settings = createCurrentSettingsRepository(); - try { - if ((await settings.get(HOST_STATUS_CONFIG_MIGRATED)) === "true") { - return { skipped: true, hostsMoved: 0 }; - } - } catch { - return { skipped: true, hostsMoved: 0 }; - } - - let hostsMoved = 0; - try { - // Raw SQL: schema.ts no longer declares stats_config. - const rows = await selectRows<{ id: number; stats_config: string | null }>( - sql`SELECT id, stats_config FROM ssh_data WHERE stats_config IS NOT NULL`, - ); - for (const row of rows) { - const config = statusConfigFromStatsConfig(row.stats_config); - if (config.statusCheckEnabled && config.statusCheckInterval === null) { - continue; - } - // TRUE and FALSE as literals: SQLite cannot bind a JS boolean. - await runStatement(sql` - UPDATE ssh_data - SET status_check_enabled = ${sql.raw(config.statusCheckEnabled ? "TRUE" : "FALSE")}, - status_check_interval = ${config.statusCheckInterval} - WHERE id = ${row.id} - `); - hostsMoved++; - } - } catch { - // A fresh install never had the column. - } - - try { - await settings.set(HOST_STATUS_CONFIG_MIGRATED, "true"); - } catch (error) { - databaseLogger.warn("Could not mark the host status migration done", { - operation: "host_status_config_migration", - error: error instanceof Error ? error.message : String(error), - }); - } - - if (hostsMoved > 0) { - databaseLogger.info( - `Moved status check options for ${hostsMoved} host(s) out of stats_config`, - { operation: "host_status_config_migration" }, - ); - } - return { skipped: false, hostsMoved }; -} diff --git a/src/backend/utils/crypto-migration/ssh-options-migration.ts b/src/backend/utils/crypto-migration/ssh-options-migration.ts deleted file mode 100644 index 63efc9124..000000000 --- a/src/backend/utils/crypto-migration/ssh-options-migration.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copies each host's SSH connection options out of ssh_data.terminal_config - * into ssh_data.ssh_options: keepalive, legacy algorithms, the agent socket - * and identity, agent forwarding and environment variables. The terminal's - * look and behavior in the same JSON belong to the ssh-terminal plugin and - * are moved by upgrade/ssh-terminal-host-settings-migration.ts. - * - * terminal_config keeps its values; nothing reads these keys from it any - * more. Old Cloudflare Access keys are left behind on purpose. - * - * Idempotent: only rows whose ssh_options is still null are read, and every - * row it reads gets a value, even an empty object. - */ - -import { sql } from "drizzle-orm"; -import { databaseLogger } from "../logger.js"; -import { runStatement, selectRows } from "./raw-rows.js"; -import { parseSshOptions } from "../../hosts/ssh-options.js"; - -export interface SshOptionsMigrationResult { - hostsMoved: number; -} - -export async function runSshOptionsMigration(): Promise { - let hostsMoved = 0; - try { - const rows = await selectRows<{ - id: number; - terminal_config: string | null; - }>(sql` - SELECT id, terminal_config FROM ssh_data - WHERE ssh_options IS NULL AND terminal_config IS NOT NULL - `); - for (const row of rows) { - const options = parseSshOptions(row.terminal_config); - await runStatement(sql` - UPDATE ssh_data SET ssh_options = ${JSON.stringify(options)} - WHERE id = ${row.id} AND ssh_options IS NULL - `); - if (Object.keys(options).length > 0) hostsMoved++; - } - } catch (error) { - databaseLogger.warn("SSH options migration failed", { - operation: "ssh_options_migration", - error: error instanceof Error ? error.message : String(error), - }); - return { hostsMoved }; - } - - if (hostsMoved > 0) { - databaseLogger.info( - `Moved SSH options for ${hostsMoved} host(s) out of terminal_config`, - { operation: "ssh_options_migration" }, - ); - } - return { hostsMoved }; -} diff --git a/src/backend/utils/field-crypto.ts b/src/backend/utils/field-crypto.ts index 5f9a924cc..2deffd9bd 100644 --- a/src/backend/utils/field-crypto.ts +++ b/src/backend/utils/field-crypto.ts @@ -15,7 +15,7 @@ class FieldCrypto { private static readonly SALT_LENGTH = 32; private static readonly ENCRYPTED_FIELDS = { - users: new Set(["passwordHash", "clientSecret", "oidcIdentifier"]), + users: new Set(["passwordHash"]), ssh_data: new Set([ "password", "key", diff --git a/src/backend/utils/lazy-field-encryption.ts b/src/backend/utils/lazy-field-encryption.ts index dfeec04b4..09f15ea60 100644 --- a/src/backend/utils/lazy-field-encryption.ts +++ b/src/backend/utils/lazy-field-encryption.ts @@ -11,10 +11,8 @@ export class LazyFieldEncryption { private_key: "privateKey", public_key: "publicKey", password_hash: "passwordHash", - client_secret: "clientSecret", totp_secret: "totpSecret", totp_backup_codes: "totpBackupCodes", - oidc_identifier: "oidcIdentifier", keyPassword: "key_password", sudoPassword: "sudo_password", @@ -22,10 +20,8 @@ export class LazyFieldEncryption { privateKey: "private_key", publicKey: "public_key", passwordHash: "password_hash", - clientSecret: "client_secret", totpSecret: "totp_secret", totpBackupCodes: "totp_backup_codes", - oidcIdentifier: "oidc_identifier", }; static isPlaintextField(value: string): boolean { @@ -95,8 +91,6 @@ export class LazyFieldEncryption { "socks5Password", "privateKey", "publicKey", - "clientSecret", - "oidcIdentifier", ]; if (sensitiveFields.includes(fieldName)) { @@ -241,8 +235,6 @@ export class LazyFieldEncryption { publicKey: "public_key", sudoPassword: "sudo_password", socks5Password: "socks5_password", - clientSecret: "client_secret", - oidcIdentifier: "oidc_identifier", }; static getSensitiveFieldsForTable(tableName: string): string[] { diff --git a/src/backend/utils/request-origin.ts b/src/backend/utils/request-origin.ts index 607018671..988ba080f 100644 --- a/src/backend/utils/request-origin.ts +++ b/src/backend/utils/request-origin.ts @@ -112,7 +112,10 @@ export function getRequestOrigin(req: Request | IncomingMessage): string { function getRequestOriginWithForceHTTPS( req: Request | IncomingMessage, ): string { - if (process.env.OIDC_FORCE_HTTPS === "true") { + // OIDC_FORCE_HTTPS is the 2.9 name, still read for one release. + const forceHttps = + process.env.EXTERNAL_FORCE_HTTPS ?? process.env.OIDC_FORCE_HTTPS; + if (forceHttps === "true") { const origin = getRequestOrigin(req); return origin.replace(/^http:/, "https:"); } diff --git a/src/types/ui-preferences.ts b/src/types/ui-preferences.ts index 673a3544d..ed39c2e5b 100644 --- a/src/types/ui-preferences.ts +++ b/src/types/ui-preferences.ts @@ -314,41 +314,10 @@ function sanitizePluginArea(input: unknown): Record { return out; } -/** - * Areas that moved into a plugin: before `version` they were stored under - * core's name. Version 1 had docker and host metrics, version 2 the terminal - * and the file manager. - */ -const MOVED_PLUGIN_AREAS: Array<{ - before: number; - legacy: string; - area: UiPluginAreaKey; -}> = [ - { before: 2, legacy: "docker", area: "plugin:docker" }, // plugin-id-ok: 2.8 key - { before: 2, legacy: "hostMetrics", area: "plugin:host-metrics" }, - { before: 3, legacy: "terminal", area: "plugin:ssh-terminal" }, - { before: 3, legacy: "fileManager", area: "plugin:file-manager" }, -]; - -export function sanitizeUiOverrides( - input: unknown, - version = UI_PREFERENCES_VERSION, -): UiOverrides { +export function sanitizeUiOverrides(input: unknown): UiOverrides { const out: Record> = {}; if (!input || typeof input !== "object") return out as UiOverrides; - const moves = MOVED_PLUGIN_AREAS.filter((move) => version < move.before); - if (moves.length > 0) { - const upgraded = { ...(input as Record) }; - for (const { legacy, area } of moves) { - if (legacy in upgraded) { - upgraded[area] = upgraded[legacy]; - delete upgraded[legacy]; - } - } - input = upgraded; - } - const specsByArea = AREA_SPECS as unknown as Record< string, Record @@ -424,10 +393,7 @@ export function sanitizeUiPreferences(input: unknown): UiPreferences { preset: PRESET_VALUES.includes(obj.preset as UiPreset) ? (obj.preset as UiPreset) : defaults.preset, - overrides: sanitizeUiOverrides( - obj.overrides, - typeof obj.version === "number" ? obj.version : 1, - ), + overrides: sanitizeUiOverrides(obj.overrides), onboarding: sanitizeOnboarding(obj.onboarding), }; } diff --git a/src/ui/auth/Auth.tsx b/src/ui/auth/Auth.tsx index 7d6557daa..cc86e1137 100644 --- a/src/ui/auth/Auth.tsx +++ b/src/ui/auth/Auth.tsx @@ -420,7 +420,7 @@ export function Auth({ onLogin }: AuthProps) { ); return; } - toast.error(event.data.error || t("errors.failedOidcLogin")); + toast.error(event.data.error || t("errors.failedExternalLogin")); }; window.addEventListener("message", handleExternalBrowserResult); @@ -604,7 +604,7 @@ export function Auth({ onLogin }: AuthProps) { toast.error(t("messages.userNotAllowed")); else if (error === "second_factor_unavailable") toast.error(t("auth.secondFactorUnavailable")); - else toast.error(`${t("errors.oidcAuthFailed")}: ${error}`); + else toast.error(`${t("errors.externalAuthFailed")}: ${error}`); window.history.replaceState({}, document.title, window.location.pathname); return; } @@ -1047,7 +1047,7 @@ export function Auth({ onLogin }: AuthProps) { throw new Error(t("errors.invalidAuthUrl")); window.location.replace(authUrl); } catch (err: unknown) { - showLoginError(err, "errors.failedOidcLogin"); + showLoginError(err, "errors.failedExternalLogin"); setLoading(false); } }, @@ -1133,7 +1133,7 @@ export function Auth({ onLogin }: AuthProps) { } if (urlTriggered) { - toast.info(t("errors.silentSigninOidcUnavailable")); + toast.info(t("errors.silentSigninExternalUnavailable")); } }, [startRedirect, authMethodsLoaded, externalMethods, t]); diff --git a/src/ui/auth/ElectronLoginForm.tsx b/src/ui/auth/ElectronLoginForm.tsx index 2b79bf342..a2a8c23c0 100644 --- a/src/ui/auth/ElectronLoginForm.tsx +++ b/src/ui/auth/ElectronLoginForm.tsx @@ -138,7 +138,7 @@ export function ElectronLoginForm({ if (!electronAPI?.externalBrowserLogin) { sendResultToIframe({ success: false, - error: t("errors.failedOidcLogin"), + error: t("errors.failedExternalLogin"), }); return; } @@ -162,7 +162,7 @@ export function ElectronLoginForm({ } sendResultToIframe({ success: false, - error: result.error || t("errors.failedOidcLogin"), + error: result.error || t("errors.failedExternalLogin"), }); } } catch (err) { @@ -172,7 +172,7 @@ export function ElectronLoginForm({ typeof event.data.providerId === "number" ? event.data.providerId : undefined; - const error = getErrorMessage(err, t("errors.failedOidcLogin")); + const error = getErrorMessage(err, t("errors.failedExternalLogin")); iframeRef.current?.contentWindow?.postMessage( { type: "OIDC_SYSTEM_BROWSER_AUTH_RESULT", diff --git a/src/ui/components/connection/ConnectionLogPanel.tsx b/src/ui/components/connection/ConnectionLogPanel.tsx index 0075a8fa7..7a1f17c48 100644 --- a/src/ui/components/connection/ConnectionLogPanel.tsx +++ b/src/ui/components/connection/ConnectionLogPanel.tsx @@ -88,8 +88,8 @@ export function ConnectionLogPanel({ .join("\n"); const ok = await copyToClipboard(logsText); - if (ok) toast.success(t("terminal.connectionLogCopied")); - else toast.error(t("terminal.connectionLogCopyFailed")); + if (ok) toast.success(t("sshAuth.connectionLogCopied")); + else toast.error(t("sshAuth.connectionLogCopyFailed")); }; const getIcon = (type: string) => { @@ -147,7 +147,7 @@ export function ConnectionLogPanel({ )} - {t("terminal.connectionLogTitle")} ({logs.length}) + {t("sshAuth.connectionLogTitle")} ({logs.length}) {logs.length > 0 && ( @@ -155,7 +155,7 @@ export function ConnectionLogPanel({ variant="ghost" size="icon-sm" onClick={copyLogsToClipboard} - title={t("terminal.connectionLogCopy")} + title={t("sshAuth.connectionLogCopy")} > @@ -167,8 +167,8 @@ export function ConnectionLogPanel({ {logs.length === 0 ? (
{isConnecting - ? t("terminal.connectionLogWaiting") - : t("terminal.connectionLogEmpty")} + ? t("sshAuth.connectionLogWaiting") + : t("sshAuth.connectionLogEmpty")}
) : (
diff --git a/src/ui/locales/en.json b/src/ui/locales/en.json index 4bc6beefa..ad7380dd1 100644 --- a/src/ui/locales/en.json +++ b/src/ui/locales/en.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "Force Keyboard-Interactive", "overrideCredentialUsername": "Override Credential Username", "overrideCredentialUsernameDesc": "Use the username specified above instead of the credential's username", - "oidcUsernameHint": "Use $external.username to fill in the name you signed in with through SSO or LDAP.", + "externalUsernameHint": "Use $external.username to fill in the name you signed in with through SSO or LDAP.", "jumpHostChain": "Jump Host Chain", "portKnocking": "Port Knocking", "addKnock": "Add Port", @@ -977,7 +977,7 @@ "noSearchResults": "No connections match your search", "rename": "Rename session" }, - "terminal": { + "sshAuth": { "totpRequired": "Two-Factor Authentication Required", "totpCodeLabel": "Verification Code", "totpVerify": "Verify", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "Failed to verify reset code", "failedCompleteReset": "Failed to complete password reset", "invalidTotpCode": "Invalid TOTP code", - "failedOidcLogin": "Failed to start external sign-in", - "silentSigninOidcUnavailable": "Silent sign-in was requested, but no external sign-in is set up.", + "failedExternalLogin": "Failed to start external sign-in", + "silentSigninExternalUnavailable": "Silent sign-in was requested, but no external sign-in is set up.", "failedUserInfo": "Failed to get user info after login", - "oidcAuthFailed": "External sign-in failed", + "externalAuthFailed": "External sign-in failed", "invalidAuthUrl": "Invalid authorization URL received from backend", "requiredField": "This field is required", "minLength": "Minimum length is {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "Let new users self-register with a username and password", "allowPasswordLogin": "Allow Password Login", "allowPasswordLoginDesc": "Username/password login", - "oidcAutoProvision": "Auto-create external accounts", - "oidcAutoProvisionDesc": "Create an account the first time someone signs in through an external login such as SSO or LDAP, even when registration is off", + "externalAutoProvision": "Auto-create external accounts", + "externalAutoProvisionDesc": "Create an account the first time someone signs in through an external login such as SSO or LDAP, even when registration is off", "secondFactorAfterExternalLogin": "Ask for a second factor after external logins (SSO, GitHub, Google, LDAP)", "secondFactorAfterExternalLoginDesc": "Off, an enrolled second factor is only asked for after password and other local logins, same as before. On, it is asked for after every login method, including SSO and LDAP.", "allowPasswordReset": "Allow Password Reset", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "No expiry", "revokedBadge": "REVOKED", "authTypeDual": "Dual Auth", - "authTypeOidc": "External", + "authTypeExternal": "External", "authTypeLocal": "Local", "adminStatusAdministrator": "Administrator", "adminStatusRegularUser": "Regular User", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "Allow both external and password sign-in", "linkAccountTargetUsername": "Local Account Username", "linkAccountTargetPlaceholder": "Enter the local account username to link to", - "linkAccountOidcUsername": "External Account Username", - "linkAccountOidcPlaceholder": "Enter the external-only account username to merge in", - "linkAccountOidcNotFound": "No external-only account found with that username", + "linkAccountExternalUsername": "External Account Username", + "linkAccountExternalPlaceholder": "Enter the external-only account username to merge in", + "linkAccountExternalNotFound": "No external-only account found with that username", "linkAccounts": "Link Accounts", "linkAccountSuccess": "Accounts linked successfully", "linkAccountFailed": "Failed to link accounts", @@ -1400,7 +1400,6 @@ "saving": "Saving...", "updateRegistrationFailed": "Failed to update registration setting", "updatePasswordLoginFailed": "Failed to update password login setting", - "updateOidcAutoProvisionFailed": "Failed to update the auto-create setting", "updateSecondFactorAfterExternalLoginFailed": "Failed to update second factor setting", "updatePasswordResetFailed": "Failed to update password reset setting", "sessionTimeoutRange2": "Session timeout must be between 1 and 720 hours", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "LOCKED", "dataLockedNotice": "{{username}}'s data stays locked until they next log in. Their hosts, credentials and snippets cannot be viewed or edited until then.", "resetPasswordTitle": "Reset Password", - "resetPasswordOidcOnly": "This user signs in through an external provider and has no password.", + "resetPasswordExternalOnly": "This user signs in through an external provider and has no password.", "resetPasswordPlaceholder": "New password", "resetPasswordBtn": "Reset", "resetPasswordWorking": "Resetting...", @@ -1742,7 +1741,7 @@ "importFailed": "Import failed: {{error}}", "roleUser": "User", "authMethodDual": "Dual Auth", - "authMethodOidc": "External", + "authMethodExternal": "External", "done": "Done", "apiKeyNameRequired": "Key name is required", "apiKeyCreated": "API key \"{{name}}\" created", diff --git a/src/ui/locales/translated/af_ZA.json b/src/ui/locales/translated/af_ZA.json index a509a4b18..cb492165c 100644 --- a/src/ui/locales/translated/af_ZA.json +++ b/src/ui/locales/translated/af_ZA.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "Forseer sleutelbord-interaktief", "overrideCredentialUsername": "Oorskryf geloofsbriewe gebruikersnaam", "overrideCredentialUsernameDesc": "Gebruik die gebruikersnaam wat hierbo gespesifiseer is in plaas van die gebruikersnaam van die geloofsbriewe", - "oidcUsernameHint": "Gebruik $external.username om die naam in te vul waarmee jy aangemeld het via SSO of LDAP.", + "externalUsernameHint": "Gebruik $external.username om die naam in te vul waarmee jy aangemeld het via SSO of LDAP.", "jumpHostChain": "Springgasheerketting", "portKnocking": "Port Knocking", "addKnock": "Voeg poort by", @@ -977,7 +977,7 @@ "noSearchResults": "Geen verbindings stem ooreen met jou soektog nie", "rename": "Hernoem sessie" }, - "terminal": { + "sshAuth": { "totpRequired": "Tweefaktor-verifikasie vereis", "totpCodeLabel": "Verifikasiekode", "totpVerify": "Verifieer", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "Kon nie die terugstelkode verifieer nie", "failedCompleteReset": "Kon nie wagwoordterugstelling voltooi nie", "invalidTotpCode": "Ongeldige TOTP-kode", - "failedOidcLogin": "Kon nie eksterne aanmelding begin nie", - "silentSigninOidcUnavailable": "Stille aanmelding is versoek, maar geen eksterne aanmelding is opgestel nie.", + "failedExternalLogin": "Kon nie eksterne aanmelding begin nie", + "silentSigninExternalUnavailable": "Stille aanmelding is versoek, maar geen eksterne aanmelding is opgestel nie.", "failedUserInfo": "Kon nie gebruikersinligting kry na aanmelding nie", - "oidcAuthFailed": "Eksterne aanmelding het misluk", + "externalAuthFailed": "Eksterne aanmelding het misluk", "invalidAuthUrl": "Ongeldige magtigings-URL ontvang van backend", "requiredField": "Hierdie veld is verpligtend", "minLength": "Minimum lengte is {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "Laat nuwe gebruikers hulself registreer met 'n gebruikersnaam en wagwoord", "allowPasswordLogin": "Laat Wagwoord Aanmelding Toe", "allowPasswordLoginDesc": "Gebruikersnaam/wagwoord aanmelding", - "oidcAutoProvision": "Skep eksterne rekeninge outomaties", - "oidcAutoProvisionDesc": "Skep 'n rekening die eerste keer wat iemand aanmeld deur 'n eksterne aanmelding soos SSO of LDAP, selfs wanneer registrasie afgeskakel is.", + "externalAutoProvision": "Skep eksterne rekeninge outomaties", + "externalAutoProvisionDesc": "Skep 'n rekening die eerste keer wat iemand aanmeld deur 'n eksterne aanmelding soos SSO of LDAP, selfs wanneer registrasie afgeskakel is.", "secondFactorAfterExternalLogin": "Vra vir 'n tweede faktor na eksterne aanmeldings (SSO, GitHub, Google, LDAP)", "secondFactorAfterExternalLoginDesc": "Af, 'n ingeskrewe tweede faktor word slegs gevra na wagwoord en ander plaaslike aanmeldings, dieselfde as voorheen. Aan, dit word gevra na elke aanmeldmetode, insluitend SSO en LDAP.", "allowPasswordReset": "Laat wagwoordherstel toe", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "Geen vervaldatum", "revokedBadge": "HERROEP", "authTypeDual": "Dubbele Magtiging", - "authTypeOidc": "Eksterne", + "authTypeExternal": "Eksterne", "authTypeLocal": "Plaaslik", "adminStatusAdministrator": "Administrateur", "adminStatusRegularUser": "Gereelde Gebruiker", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "Laat beide eksterne en wagwoordaanmelding toe", "linkAccountTargetUsername": "Gebruikersnaam van die plaaslike rekening", "linkAccountTargetPlaceholder": "Voer die plaaslike rekeninggebruikersnaam in om aan te koppel", - "linkAccountOidcUsername": "Eksterne rekening gebruikersnaam", - "linkAccountOidcPlaceholder": "Voer die gebruikersnaam van die slegs-eksterne rekening in om saam te voeg", - "linkAccountOidcNotFound": "Geen eksterne rekening gevind met daardie gebruikersnaam nie", + "linkAccountExternalUsername": "Eksterne rekening gebruikersnaam", + "linkAccountExternalPlaceholder": "Voer die gebruikersnaam van die slegs-eksterne rekening in om saam te voeg", + "linkAccountExternalNotFound": "Geen eksterne rekening gevind met daardie gebruikersnaam nie", "linkAccounts": "Koppel rekeninge", "linkAccountSuccess": "Rekeninge suksesvol gekoppel", "linkAccountFailed": "Kon nie rekeninge koppel nie", @@ -1400,7 +1400,6 @@ "saving": "Stoor...", "updateRegistrationFailed": "Kon nie registrasie-instellings opdateer nie", "updatePasswordLoginFailed": "Kon nie wagwoord-aanmeldinstelling opdateer nie", - "updateOidcAutoProvisionFailed": "Kon nie die outomatiese skep-instelling opdateer nie", "updateSecondFactorAfterExternalLoginFailed": "Kon nie die tweede faktorinstelling opdateer nie", "updatePasswordResetFailed": "Kon nie wagwoordterugstellinginstelling opdateer nie", "sessionTimeoutRange2": "Sessie-tydsberekening moet tussen 1 en 720 uur wees", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "GESLUIT", "dataLockedNotice": "{{username}}se data bly gesluit totdat hulle weer aanmeld. Hul gashere, geloofsbriewe en brokkies kan nie tot dan besigtig of geredigeer word nie.", "resetPasswordTitle": "Herstel wagwoord", - "resetPasswordOidcOnly": "Hierdie gebruiker meld aan deur 'n eksterne verskaffer en het geen wagwoord nie.", + "resetPasswordExternalOnly": "Hierdie gebruiker meld aan deur 'n eksterne verskaffer en het geen wagwoord nie.", "resetPasswordPlaceholder": "Nuwe wagwoord", "resetPasswordBtn": "Herstel", "resetPasswordWorking": "Herstel tans...", @@ -1742,7 +1741,7 @@ "importFailed": "Invoer het misluk: {{error}}", "roleUser": "Gebruiker", "authMethodDual": "Dubbele Magtiging", - "authMethodOidc": "Eksterne", + "authMethodExternal": "Eksterne", "done": "Klaar", "apiKeyNameRequired": "Sleutelnaam word vereis", "apiKeyCreated": "API-sleutel \"{{name}}\" geskep", diff --git a/src/ui/locales/translated/ar_SA.json b/src/ui/locales/translated/ar_SA.json index c1b53c096..9c912ff34 100644 --- a/src/ui/locales/translated/ar_SA.json +++ b/src/ui/locales/translated/ar_SA.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "فرض تفاعل لوحة المفاتيح", "overrideCredentialUsername": "تجاوز اسم مستخدم الاعتماد", "overrideCredentialUsernameDesc": "استخدم اسم المستخدم المحدد أعلاه بدلاً من اسم مستخدم الاعتماد", - "oidcUsernameHint": "استخدم $external.username لملء الاسم الذي قمت بتسجيل الدخول به من خلال SSO أو LDAP.", + "externalUsernameHint": "استخدم $external.username لملء الاسم الذي قمت بتسجيل الدخول به من خلال SSO أو LDAP.", "jumpHostChain": "سلسلة مضيفات الانتقال", "portKnocking": "طرق المنفذ", "addKnock": "إضافة منفذ", @@ -977,7 +977,7 @@ "noSearchResults": "لا توجد اتصالات تطابق بحثك", "rename": "إعادة تسمية الجلسة" }, - "terminal": { + "sshAuth": { "totpRequired": "مطلوب المصادقة الثنائية", "totpCodeLabel": "رمز التحقق", "totpVerify": "تحقق", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "فشل التحقق من رمز إعادة التعيين", "failedCompleteReset": "فشل إكمال إعادة تعيين كلمة المرور", "invalidTotpCode": "رمز TOTP غير صالح", - "failedOidcLogin": "فشل بدء تسجيل الدخول الخارجي", - "silentSigninOidcUnavailable": "تم طلب تسجيل الدخول الصامت، ولكن لم يتم إعداد أي تسجيل دخول خارجي.", + "failedExternalLogin": "فشل بدء تسجيل الدخول الخارجي", + "silentSigninExternalUnavailable": "تم طلب تسجيل الدخول الصامت، ولكن لم يتم إعداد أي تسجيل دخول خارجي.", "failedUserInfo": "فشل الحصول على معلومات المستخدم بعد تسجيل الدخول", - "oidcAuthFailed": "فشل تسجيل الدخول الخارجي", + "externalAuthFailed": "فشل تسجيل الدخول الخارجي", "invalidAuthUrl": "تم استلام عنوان URL تفويض غير صالح من الخادم الخلفي", "requiredField": "هذا الحقل مطلوب", "minLength": "الحد الأدنى للطول هو {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "يسمح للمستخدمين الجدد بالتسجيل الذاتي باستخدام اسم مستخدم وكلمة مرور", "allowPasswordLogin": "السماح بتسجيل الدخول بكلمة المرور", "allowPasswordLoginDesc": "تسجيل الدخول باسم المستخدم/كلمة المرور", - "oidcAutoProvision": "إنشاء حسابات خارجية تلقائياً", - "oidcAutoProvisionDesc": "أنشئ حسابًا في المرة الأولى التي يسجل فيها شخص ما الدخول من خلال تسجيل دخول خارجي مثل SSO أو LDAP، حتى عندما يكون التسجيل معطلاً", + "externalAutoProvision": "إنشاء حسابات خارجية تلقائياً", + "externalAutoProvisionDesc": "أنشئ حسابًا في المرة الأولى التي يسجل فيها شخص ما الدخول من خلال تسجيل دخول خارجي مثل SSO أو LDAP، حتى عندما يكون التسجيل معطلاً", "secondFactorAfterExternalLogin": "اطلب عامل مصادقة ثانٍ بعد عمليات تسجيل الدخول الخارجية (SSO، GitHub، Google، LDAP)", "secondFactorAfterExternalLoginDesc": "في حالة عدم تفعيل عامل التحقق الثاني، يُطلب فقط بعد إدخال كلمة المرور وتسجيلات الدخول المحلية الأخرى، كما كان الحال سابقًا. أما في حالة تفعيله، فيُطلب بعد كل طريقة تسجيل دخول، بما في ذلك تسجيل الدخول الموحد (SSO) وبروتوكول LDAP.", "allowPasswordReset": "السماح بإعادة تعيين كلمة المرور", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "بدون انتهاء صلاحية", "revokedBadge": "ملغى", "authTypeDual": "مصادقة مزدوجة", - "authTypeOidc": "خارجي", + "authTypeExternal": "خارجي", "authTypeLocal": "محلي", "adminStatusAdministrator": "مدير", "adminStatusRegularUser": "مستخدم عادي", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "السماح بتسجيل الدخول الخارجي وتسجيل الدخول باستخدام كلمة المرور", "linkAccountTargetUsername": "اسم مستخدم الحساب المحلي", "linkAccountTargetPlaceholder": "أدخل اسم مستخدم الحساب المحلي للربط به", - "linkAccountOidcUsername": "اسم مستخدم الحساب الخارجي", - "linkAccountOidcPlaceholder": "أدخل اسم مستخدم الحساب الخارجي فقط لدمجه", - "linkAccountOidcNotFound": "لم يتم العثور على حساب خارجي فقط بهذا الاسم المستخدم", + "linkAccountExternalUsername": "اسم مستخدم الحساب الخارجي", + "linkAccountExternalPlaceholder": "أدخل اسم مستخدم الحساب الخارجي فقط لدمجه", + "linkAccountExternalNotFound": "لم يتم العثور على حساب خارجي فقط بهذا الاسم المستخدم", "linkAccounts": "ربط الحسابات", "linkAccountSuccess": "تم ربط الحسابات بنجاح", "linkAccountFailed": "فشل ربط الحسابات", @@ -1400,7 +1400,6 @@ "saving": "جارٍ الحفظ...", "updateRegistrationFailed": "فشل تحديث إعداد التسجيل", "updatePasswordLoginFailed": "فشل تحديث إعداد تسجيل الدخول بكلمة المرور", - "updateOidcAutoProvisionFailed": "فشل تحديث إعداد الإنشاء التلقائي", "updateSecondFactorAfterExternalLoginFailed": "فشل تحديث إعدادات العامل الثاني", "updatePasswordResetFailed": "فشل تحديث إعداد إعادة تعيين كلمة المرور", "sessionTimeoutRange2": "يجب أن تكون مهلة الجلسة بين 1 و 720 ساعة", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "مقفل", "dataLockedNotice": "تظل بيانات {{username}} مقفلة حتى تسجيل الدخول التالي. لا يمكن عرض أو تعديل مضيفيه وبيانات اعتماده ومقاطع حتى ذلك الحين.", "resetPasswordTitle": "إعادة تعيين كلمة المرور", - "resetPasswordOidcOnly": "يسجل هذا المستخدم الدخول عبر مزود خارجي ولا يملك كلمة مرور.", + "resetPasswordExternalOnly": "يسجل هذا المستخدم الدخول عبر مزود خارجي ولا يملك كلمة مرور.", "resetPasswordPlaceholder": "كلمة مرور جديدة", "resetPasswordBtn": "إعادة تعيين", "resetPasswordWorking": "جارٍ إعادة التعيين...", @@ -1742,7 +1741,7 @@ "importFailed": "فشل الاستيراد: {{error}}", "roleUser": "مستخدم", "authMethodDual": "مصادقة مزدوجة", - "authMethodOidc": "خارجي", + "authMethodExternal": "خارجي", "done": "تم", "apiKeyNameRequired": "اسم المفتاح مطلوب", "apiKeyCreated": "تم إنشاء مفتاح API \"{{name}}\"", diff --git a/src/ui/locales/translated/bg_BG.json b/src/ui/locales/translated/bg_BG.json index 3154bcf17..0bd8e6ba1 100644 --- a/src/ui/locales/translated/bg_BG.json +++ b/src/ui/locales/translated/bg_BG.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "Принудително интерактивно с клавиатура", "overrideCredentialUsername": "Замяна на потребителско име за идентификационни данни", "overrideCredentialUsernameDesc": "Използвайте посоченото по-горе потребителско име вместо потребителското име от идентификационните данни", - "oidcUsernameHint": "Използвайте $external.username, за да попълните името, с което сте влезли чрез SSO или LDAP.", + "externalUsernameHint": "Използвайте $external.username, за да попълните името, с което сте влезли чрез SSO или LDAP.", "jumpHostChain": "Верига за прескачане на хост", "portKnocking": "Порт Нокинг", "addKnock": "Добавяне на порт", @@ -977,7 +977,7 @@ "noSearchResults": "Няма връзки, които да отговарят на търсенето ви", "rename": "Преименуване на сесия" }, - "terminal": { + "sshAuth": { "totpRequired": "Изисква се двуфакторно удостоверяване", "totpCodeLabel": "Код за потвърждение", "totpVerify": "Проверка", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "Неуспешно потвърждаване на кода за нулиране", "failedCompleteReset": "Неуспешно завършване на нулирането на паролата", "invalidTotpCode": "Невалиден TOTP код", - "failedOidcLogin": "Стартирането на външно влизане не бе успешно", - "silentSigninOidcUnavailable": "Заявено е безшумно влизане, но не е настроено външно влизане.", + "failedExternalLogin": "Стартирането на външно влизане не бе успешно", + "silentSigninExternalUnavailable": "Заявено е безшумно влизане, но не е настроено външно влизане.", "failedUserInfo": "Неуспешно получаване на потребителска информация след влизане", - "oidcAuthFailed": "Външното влизане не бе успешно", + "externalAuthFailed": "Външното влизане не бе успешно", "invalidAuthUrl": "Невалиден URL адрес за оторизация, получен от бекенд системата", "requiredField": "Това поле е задължително", "minLength": "Минималната дължина е {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "Позволете на новите потребители да се регистрират сами с потребителско име и парола", "allowPasswordLogin": "Разрешаване на вход с парола", "allowPasswordLoginDesc": "Вход с потребителско име/парола", - "oidcAutoProvision": "Автоматично създаване на външни акаунти", - "oidcAutoProvisionDesc": "Създайте акаунт при първото влизане на някого чрез външен вход, като например SSO или LDAP, дори когато регистрацията е изключена", + "externalAutoProvision": "Автоматично създаване на външни акаунти", + "externalAutoProvisionDesc": "Създайте акаунт при първото влизане на някого чрез външен вход, като например SSO или LDAP, дори когато регистрацията е изключена", "secondFactorAfterExternalLogin": "Искайте втори фактор след външни входове (SSO, GitHub, Google, LDAP)", "secondFactorAfterExternalLoginDesc": "Изключено, регистриран втори фактор се изисква само след парола и други локални входове, както преди. Включено, той се изисква след всеки метод за вход, включително SSO и LDAP.", "allowPasswordReset": "Разрешаване на нулиране на паролата", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "Без срок на годност", "revokedBadge": "ОТМЕНЕНО", "authTypeDual": "Двойно удостоверяване", - "authTypeOidc": "Външен", + "authTypeExternal": "Външен", "authTypeLocal": "Местно", "adminStatusAdministrator": "Администратор", "adminStatusRegularUser": "Редовен потребител", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "Разрешаване на външно влизане и влизане с парола", "linkAccountTargetUsername": "Потребителско име за локален акаунт", "linkAccountTargetPlaceholder": "Въведете потребителското име на локалния акаунт, към който да се свържете", - "linkAccountOidcUsername": "Потребителско име за външен акаунт", - "linkAccountOidcPlaceholder": "Въведете потребителското име само за външен акаунт, за да го обедините", - "linkAccountOidcNotFound": "Не е намерен само външен акаунт с това потребителско име", + "linkAccountExternalUsername": "Потребителско име за външен акаунт", + "linkAccountExternalPlaceholder": "Въведете потребителското име само за външен акаунт, за да го обедините", + "linkAccountExternalNotFound": "Не е намерен само външен акаунт с това потребителско име", "linkAccounts": "Свързване на акаунти", "linkAccountSuccess": "Профилите са свързани успешно", "linkAccountFailed": "Свързването на акаунти не бе успешно", @@ -1400,7 +1400,6 @@ "saving": "Запазване...", "updateRegistrationFailed": "Актуализирането на настройката за регистрация не бе успешно", "updatePasswordLoginFailed": "Актуализирането на настройката за вход с парола не бе успешно", - "updateOidcAutoProvisionFailed": "Актуализирането на настройката за автоматично създаване не бе успешно", "updateSecondFactorAfterExternalLoginFailed": "Актуализирането на настройката за втори фактор не бе успешно", "updatePasswordResetFailed": "Актуализирането на настройката за нулиране на паролата не бе успешно", "sessionTimeoutRange2": "Времето за изчакване на сесията трябва да е между 1 и 720 часа", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "ЗАКЛЮЧЕНО", "dataLockedNotice": "Данните на {{username}}остават заключени до следващото им влизане. Техните хостове, идентификационни данни и фрагменти не могат да бъдат преглеждани или редактирани дотогава.", "resetPasswordTitle": "Нулиране на парола", - "resetPasswordOidcOnly": "Този потребител влиза чрез външен доставчик и няма парола.", + "resetPasswordExternalOnly": "Този потребител влиза чрез външен доставчик и няма парола.", "resetPasswordPlaceholder": "Нова парола", "resetPasswordBtn": "Нулиране", "resetPasswordWorking": "Нулиране...", @@ -1742,7 +1741,7 @@ "importFailed": "Импортирането не бе успешно: {{error}}", "roleUser": "Потребител", "authMethodDual": "Двойно удостоверяване", - "authMethodOidc": "Външен", + "authMethodExternal": "Външен", "done": "Готово", "apiKeyNameRequired": "Името на ключа е задължително", "apiKeyCreated": "API ключът „{{name}}“ е създаден", diff --git a/src/ui/locales/translated/bn_BD.json b/src/ui/locales/translated/bn_BD.json index 26feeb9ce..a7ae15711 100644 --- a/src/ui/locales/translated/bn_BD.json +++ b/src/ui/locales/translated/bn_BD.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "ফোর্স কীবোর্ড-ইন্টারেক্টিভ", "overrideCredentialUsername": "ক্রেডেনশিয়াল ব্যবহারকারীর নাম ওভাররাইড করুন", "overrideCredentialUsernameDesc": "ক্রেডেনশিয়ালের ইউজারনেমের পরিবর্তে উপরে উল্লেখিত ইউজারনেমটি ব্যবহার করুন।", - "oidcUsernameHint": "SSO বা LDAP-এর মাধ্যমে আপনি যে নামটি দিয়ে সাইন ইন করেছেন, সেটি পূরণ করতে $external.username ব্যবহার করুন।", + "externalUsernameHint": "SSO বা LDAP-এর মাধ্যমে আপনি যে নামটি দিয়ে সাইন ইন করেছেন, সেটি পূরণ করতে $external.username ব্যবহার করুন।", "jumpHostChain": "জাম্প হোস্ট চেইন", "portKnocking": "পোর্ট নকিং", "addKnock": "পোর্ট যোগ করুন", @@ -977,7 +977,7 @@ "noSearchResults": "আপনার অনুসন্ধানের সাথে কোনো সংযোগ মেলেনি।", "rename": "পুনঃনামকরণ অধিবেশন" }, - "terminal": { + "sshAuth": { "totpRequired": "দ্বি-স্তরীয় প্রমাণীকরণ আবশ্যক", "totpCodeLabel": "যাচাইকরণ কোড", "totpVerify": "যাচাই করুন", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "রিসেট কোড যাচাই করতে ব্যর্থ হয়েছে", "failedCompleteReset": "পাসওয়ার্ড রিসেট সম্পন্ন করা যায়নি", "invalidTotpCode": "অবৈধ TOTP কোড", - "failedOidcLogin": "বাহ্যিক সাইন-ইন শুরু করতে ব্যর্থ হয়েছে", - "silentSigninOidcUnavailable": "নীরব সাইন-ইন-এর জন্য অনুরোধ করা হয়েছিল, কিন্তু কোনো বাহ্যিক সাইন-ইন সেট আপ করা নেই।", + "failedExternalLogin": "বাহ্যিক সাইন-ইন শুরু করতে ব্যর্থ হয়েছে", + "silentSigninExternalUnavailable": "নীরব সাইন-ইন-এর জন্য অনুরোধ করা হয়েছিল, কিন্তু কোনো বাহ্যিক সাইন-ইন সেট আপ করা নেই।", "failedUserInfo": "লগইন করার পর ব্যবহারকারীর তথ্য পেতে ব্যর্থ হয়েছে", - "oidcAuthFailed": "বাহ্যিক সাইন-ইন ব্যর্থ হয়েছে", + "externalAuthFailed": "বাহ্যিক সাইন-ইন ব্যর্থ হয়েছে", "invalidAuthUrl": "ব্যাকএন্ড থেকে প্রাপ্ত অনুমোদন URLটি অবৈধ।", "requiredField": "এই ক্ষেত্রটি আবশ্যক", "minLength": "সর্বনিম্ন দৈর্ঘ্য হল {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "নতুন ব্যবহারকারীদের একটি ইউজারনেম ও পাসওয়ার্ড দিয়ে স্ব-নিবন্ধন করতে দিন।", "allowPasswordLogin": "পাসওয়ার্ড লগইনের অনুমতি দিন", "allowPasswordLoginDesc": "ব্যবহারকারীর নাম/পাসওয়ার্ড লগইন", - "oidcAutoProvision": "স্বয়ংক্রিয়ভাবে বাহ্যিক অ্যাকাউন্ট তৈরি করুন", - "oidcAutoProvisionDesc": "কেউ যখন SSO বা LDAP-এর মতো কোনো বাহ্যিক লগইনের মাধ্যমে প্রথমবার সাইন ইন করে, তখন রেজিস্ট্রেশন বন্ধ থাকলেও একটি অ্যাকাউন্ট তৈরি করুন।", + "externalAutoProvision": "স্বয়ংক্রিয়ভাবে বাহ্যিক অ্যাকাউন্ট তৈরি করুন", + "externalAutoProvisionDesc": "কেউ যখন SSO বা LDAP-এর মতো কোনো বাহ্যিক লগইনের মাধ্যমে প্রথমবার সাইন ইন করে, তখন রেজিস্ট্রেশন বন্ধ থাকলেও একটি অ্যাকাউন্ট তৈরি করুন।", "secondFactorAfterExternalLogin": "বাহ্যিক লগইনের (এসএসও, গিটহাব, গুগল, এলড্যাপ) পর দ্বিতীয় ফ্যাক্টরের জন্য অনুরোধ করুন।", "secondFactorAfterExternalLoginDesc": "অফ থাকলে, আগের মতোই শুধুমাত্র পাসওয়ার্ড এবং অন্যান্য লোকাল লগইনের পরেই নথিভুক্ত দ্বিতীয় ফ্যাক্টরটি চাওয়া হয়। অন থাকলে, SSO এবং LDAP সহ প্রতিটি লগইন পদ্ধতির পরেই এটি চাওয়া হয়।", "allowPasswordReset": "পাসওয়ার্ড রিসেট করার অনুমতি দিন", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "কোনো মেয়াদ নেই", "revokedBadge": "বাতিল করা হয়েছে", "authTypeDual": "দ্বৈত প্রমাণীকরণ", - "authTypeOidc": "বাহ্যিক", + "authTypeExternal": "বাহ্যিক", "authTypeLocal": "স্থানীয়", "adminStatusAdministrator": "প্রশাসক", "adminStatusRegularUser": "নিয়মিত ব্যবহারকারী", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "বাহ্যিক এবং পাসওয়ার্ড উভয় সাইন-ইনের অনুমতি দিন", "linkAccountTargetUsername": "স্থানীয় অ্যাকাউন্টের ব্যবহারকারীর নাম", "linkAccountTargetPlaceholder": "লিঙ্ক করার জন্য স্থানীয় অ্যাকাউন্টের ব্যবহারকারীর নাম লিখুন", - "linkAccountOidcUsername": "বাহ্যিক অ্যাকাউন্টের ব্যবহারকারীর নাম", - "linkAccountOidcPlaceholder": "মার্জ করার জন্য শুধুমাত্র বাহ্যিক অ্যাকাউন্টের ইউজারনেমটি প্রবেশ করান।", - "linkAccountOidcNotFound": "এই ইউজারনেম দিয়ে কোনো শুধুমাত্র বাহ্যিক ব্যবহারের অ্যাকাউন্ট খুঁজে পাওয়া যায়নি।", + "linkAccountExternalUsername": "বাহ্যিক অ্যাকাউন্টের ব্যবহারকারীর নাম", + "linkAccountExternalPlaceholder": "মার্জ করার জন্য শুধুমাত্র বাহ্যিক অ্যাকাউন্টের ইউজারনেমটি প্রবেশ করান।", + "linkAccountExternalNotFound": "এই ইউজারনেম দিয়ে কোনো শুধুমাত্র বাহ্যিক ব্যবহারের অ্যাকাউন্ট খুঁজে পাওয়া যায়নি।", "linkAccounts": "অ্যাকাউন্ট লিঙ্ক করুন", "linkAccountSuccess": "অ্যাকাউন্টগুলি সফলভাবে লিঙ্ক করা হয়েছে", "linkAccountFailed": "অ্যাকাউন্ট লিঙ্ক করতে ব্যর্থ হয়েছে", @@ -1400,7 +1400,6 @@ "saving": "সঞ্চয়...", "updateRegistrationFailed": "নিবন্ধন সেটিং আপডেট করতে ব্যর্থ হয়েছে", "updatePasswordLoginFailed": "পাসওয়ার্ড লগইন সেটিং আপডেট করতে ব্যর্থ হয়েছে", - "updateOidcAutoProvisionFailed": "স্বয়ংক্রিয়-সৃষ্টি সেটিং আপডেট করতে ব্যর্থ হয়েছে", "updateSecondFactorAfterExternalLoginFailed": "দ্বিতীয় ফ্যাক্টর সেটিং আপডেট করতে ব্যর্থ হয়েছে", "updatePasswordResetFailed": "পাসওয়ার্ড রিসেট সেটিং আপডেট করতে ব্যর্থ হয়েছে", "sessionTimeoutRange2": "সেশন টাইমআউট অবশ্যই ১ থেকে ৭২০ ঘণ্টার মধ্যে হতে হবে।", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "লক করা", "dataLockedNotice": "{{username}}-এর ডেটা পরবর্তী লগইন পর্যন্ত লক করা থাকে। ততক্ষণ পর্যন্ত তাদের হোস্ট, ক্রেডেনশিয়াল এবং স্নিপেট দেখা বা সম্পাদনা করা যায় না।", "resetPasswordTitle": "পাসওয়ার্ড রিসেট করুন", - "resetPasswordOidcOnly": "এই ব্যবহারকারী একটি বাহ্যিক প্রদানকারীর মাধ্যমে সাইন ইন করেন এবং তার কোনো পাসওয়ার্ড নেই।", + "resetPasswordExternalOnly": "এই ব্যবহারকারী একটি বাহ্যিক প্রদানকারীর মাধ্যমে সাইন ইন করেন এবং তার কোনো পাসওয়ার্ড নেই।", "resetPasswordPlaceholder": "নতুন পাসওয়ার্ড", "resetPasswordBtn": "রিসেট", "resetPasswordWorking": "রিসেট করা হচ্ছে...", @@ -1742,7 +1741,7 @@ "importFailed": "আমদানি ব্যর্থ হয়েছে: {{error}}", "roleUser": "ব্যবহারকারী", "authMethodDual": "দ্বৈত প্রমাণীকরণ", - "authMethodOidc": "বাহ্যিক", + "authMethodExternal": "বাহ্যিক", "done": "সম্পন্ন হয়েছে", "apiKeyNameRequired": "কী-এর নাম আবশ্যক।", "apiKeyCreated": "এপিআই কী \"{{name}}\" তৈরি করা হয়েছে", diff --git a/src/ui/locales/translated/ca_ES.json b/src/ui/locales/translated/ca_ES.json index 684bd5906..852bd6a65 100644 --- a/src/ui/locales/translated/ca_ES.json +++ b/src/ui/locales/translated/ca_ES.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "Forçar la interacció del teclat", "overrideCredentialUsername": "Substitueix les credencials i el nom d'usuari", "overrideCredentialUsernameDesc": "Feu servir el nom d'usuari especificat anteriorment en comptes del nom d'usuari de la credencial", - "oidcUsernameHint": "Feu servir $external.username per omplir el nom amb què heu iniciat la sessió mitjançant SSO o LDAP.", + "externalUsernameHint": "Feu servir $external.username per omplir el nom amb què heu iniciat la sessió mitjançant SSO o LDAP.", "jumpHostChain": "Cadena d'amfitrió de salt", "portKnocking": "Cop de port", "addKnock": "Afegeix un port", @@ -977,7 +977,7 @@ "noSearchResults": "No hi ha connexions que coincideixin amb la teva cerca", "rename": "Canvia el nom de la sessió" }, - "terminal": { + "sshAuth": { "totpRequired": "Cal autenticació de dos factors", "totpCodeLabel": "Codi de verificació", "totpVerify": "Verifica", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "No s'ha pogut verificar el codi de restabliment", "failedCompleteReset": "No s'ha pogut completar el restabliment de la contrasenya", "invalidTotpCode": "Codi TOTP no vàlid", - "failedOidcLogin": "No s'ha pogut iniciar l'inici de sessió extern", - "silentSigninOidcUnavailable": "S'ha sol·licitat l'inici de sessió silenciós, però no hi ha cap inici de sessió extern configurat.", + "failedExternalLogin": "No s'ha pogut iniciar l'inici de sessió extern", + "silentSigninExternalUnavailable": "S'ha sol·licitat l'inici de sessió silenciós, però no hi ha cap inici de sessió extern configurat.", "failedUserInfo": "No s'ha pogut obtenir la informació de l'usuari després de l'inici de sessió", - "oidcAuthFailed": "L'inici de sessió extern ha fallat", + "externalAuthFailed": "L'inici de sessió extern ha fallat", "invalidAuthUrl": "URL d'autorització no vàlida rebuda del backend", "requiredField": "Aquest camp és obligatori", "minLength": "La longitud mínima és {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "Permet que els nous usuaris s'autoregistrin amb un nom d'usuari i una contrasenya", "allowPasswordLogin": "Permet l'inici de sessió amb contrasenya", "allowPasswordLoginDesc": "Inici de sessió amb nom d'usuari/contrasenya", - "oidcAutoProvision": "Crea automàticament comptes externs", - "oidcAutoProvisionDesc": "Crea un compte la primera vegada que algú iniciï la sessió mitjançant un inici de sessió extern com ara SSO o LDAP, fins i tot quan el registre estigui desactivat", + "externalAutoProvision": "Crea automàticament comptes externs", + "externalAutoProvisionDesc": "Crea un compte la primera vegada que algú iniciï la sessió mitjançant un inici de sessió extern com ara SSO o LDAP, fins i tot quan el registre estigui desactivat", "secondFactorAfterExternalLogin": "Demanar un segon factor després d'inicis de sessió externs (SSO, GitHub, Google, LDAP)", "secondFactorAfterExternalLoginDesc": "Desactivat, només es demana un segon factor inscrit després de la contrasenya i altres inicis de sessió locals, igual que abans. Activat, es demana després de cada mètode d'inici de sessió, inclosos SSO i LDAP.", "allowPasswordReset": "Permet el restabliment de la contrasenya", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "Sense caducitat", "revokedBadge": "REVOCAT", "authTypeDual": "Doble autenticació", - "authTypeOidc": "Extern", + "authTypeExternal": "Extern", "authTypeLocal": "Local", "adminStatusAdministrator": "Administrador/a", "adminStatusRegularUser": "Usuari habitual", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "Permet l'inici de sessió extern i amb contrasenya", "linkAccountTargetUsername": "Nom d'usuari del compte local", "linkAccountTargetPlaceholder": "Introduïu el nom d'usuari del compte local al qual voleu enllaçar", - "linkAccountOidcUsername": "Nom d'usuari del compte extern", - "linkAccountOidcPlaceholder": "Introduïu el nom d'usuari del compte només extern per fusionar-lo", - "linkAccountOidcNotFound": "No s'ha trobat cap compte només extern amb aquest nom d'usuari", + "linkAccountExternalUsername": "Nom d'usuari del compte extern", + "linkAccountExternalPlaceholder": "Introduïu el nom d'usuari del compte només extern per fusionar-lo", + "linkAccountExternalNotFound": "No s'ha trobat cap compte només extern amb aquest nom d'usuari", "linkAccounts": "Enllaçar comptes", "linkAccountSuccess": "Comptes enllaçats correctament", "linkAccountFailed": "No s'han pogut enllaçar els comptes", @@ -1400,7 +1400,6 @@ "saving": "Desant...", "updateRegistrationFailed": "No s'ha pogut actualitzar la configuració del registre", "updatePasswordLoginFailed": "No s'ha pogut actualitzar la configuració d'inici de sessió amb contrasenya", - "updateOidcAutoProvisionFailed": "No s'ha pogut actualitzar la configuració de creació automàtica", "updateSecondFactorAfterExternalLoginFailed": "No s'ha pogut actualitzar la configuració del segon factor", "updatePasswordResetFailed": "No s'ha pogut actualitzar la configuració de restabliment de la contrasenya", "sessionTimeoutRange2": "El temps d'espera de la sessió ha d'estar entre 1 i 720 hores", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "BLOQUEJAT", "dataLockedNotice": "Les dades de {{username}}romanen bloquejades fins que inicien la sessió de nou. Els seus amfitrions, credencials i fragments no es poden veure ni editar fins aleshores.", "resetPasswordTitle": "Restablir contrasenya", - "resetPasswordOidcOnly": "Aquest usuari inicia la sessió a través d'un proveïdor extern i no té contrasenya.", + "resetPasswordExternalOnly": "Aquest usuari inicia la sessió a través d'un proveïdor extern i no té contrasenya.", "resetPasswordPlaceholder": "Nova contrasenya", "resetPasswordBtn": "Restablir", "resetPasswordWorking": "Restableix...", @@ -1742,7 +1741,7 @@ "importFailed": "La importació ha fallat: {{error}}", "roleUser": "Usuari", "authMethodDual": "Doble autenticació", - "authMethodOidc": "Extern", + "authMethodExternal": "Extern", "done": "Fet", "apiKeyNameRequired": "El nom de la clau és obligatori", "apiKeyCreated": "Clau API \"{{name}}\" creada", diff --git a/src/ui/locales/translated/cs_CZ.json b/src/ui/locales/translated/cs_CZ.json index 4cf58e56f..5911bdbdf 100644 --- a/src/ui/locales/translated/cs_CZ.json +++ b/src/ui/locales/translated/cs_CZ.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "Vynutit interaktivní klávesnici", "overrideCredentialUsername": "Přepsat uživatelské jméno pro přihlašovací údaje", "overrideCredentialUsernameDesc": "Použijte výše uvedené uživatelské jméno místo uživatelského jména z přihlašovacích údajů.", - "oidcUsernameHint": "Použijte $external.username k vyplnění jména, pod kterým jste se přihlásili přes SSO nebo LDAP.", + "externalUsernameHint": "Použijte $external.username k vyplnění jména, pod kterým jste se přihlásili přes SSO nebo LDAP.", "jumpHostChain": "Přejít k hostitelskému řetězci", "portKnocking": "Klepnutí na přístav", "addKnock": "Přidat port", @@ -977,7 +977,7 @@ "noSearchResults": "Vašemu vyhledávání neodpovídají žádná spojení", "rename": "Přejmenovat relaci" }, - "terminal": { + "sshAuth": { "totpRequired": "Vyžadováno dvoufaktorové ověřování", "totpCodeLabel": "Ověřovací kód", "totpVerify": "Ověřit", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "Nepodařilo se ověřit resetovací kód", "failedCompleteReset": "Obnovení hesla se nepodařilo dokončit", "invalidTotpCode": "Neplatný kód TOTP", - "failedOidcLogin": "Nepodařilo se spustit externí přihlášení", - "silentSigninOidcUnavailable": "Bylo požádáno o tiché přihlášení, ale není nastaveno žádné externí přihlášení.", + "failedExternalLogin": "Nepodařilo se spustit externí přihlášení", + "silentSigninExternalUnavailable": "Bylo požádáno o tiché přihlášení, ale není nastaveno žádné externí přihlášení.", "failedUserInfo": "Po přihlášení se nepodařilo získat informace o uživateli", - "oidcAuthFailed": "Externí přihlášení se nezdařilo", + "externalAuthFailed": "Externí přihlášení se nezdařilo", "invalidAuthUrl": "Z backendu byla přijata neplatná autorizační URL.", "requiredField": "Toto pole je povinné", "minLength": "Minimální délka je {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "Umožnit novým uživatelům samoregistraci pomocí uživatelského jména a hesla", "allowPasswordLogin": "Povolit přihlášení heslem", "allowPasswordLoginDesc": "Přihlášení pomocí uživatelského jména/hesla", - "oidcAutoProvision": "Automatické vytváření externích účtů", - "oidcAutoProvisionDesc": "Vytvořte účet při prvním přihlášení uživatele pomocí externího přihlašovacího systému, jako je SSO nebo LDAP, a to i v případě, že je registrace vypnutá.", + "externalAutoProvision": "Automatické vytváření externích účtů", + "externalAutoProvisionDesc": "Vytvořte účet při prvním přihlášení uživatele pomocí externího přihlašovacího systému, jako je SSO nebo LDAP, a to i v případě, že je registrace vypnutá.", "secondFactorAfterExternalLogin": "Požadovat druhý faktor po externích přihlášeních (SSO, GitHub, Google, LDAP)", "secondFactorAfterExternalLoginDesc": "Pokud je vypnuto, je zaregistrovaný druhý faktor vyžadován pouze po zadání hesla a dalších lokálních přihlášeních, stejně jako dříve. Pokud je zapnuto, je vyžadován po každé metodě přihlášení, včetně SSO a LDAP.", "allowPasswordReset": "Povolit resetování hesla", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "Bez expirace", "revokedBadge": "ZRUŠENO", "authTypeDual": "Duální ověřování", - "authTypeOidc": "Externí", + "authTypeExternal": "Externí", "authTypeLocal": "Místní", "adminStatusAdministrator": "Správce", "adminStatusRegularUser": "Běžný uživatel", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "Povolit externí přihlášení i přihlášení pomocí hesla", "linkAccountTargetUsername": "Uživatelské jméno lokálního účtu", "linkAccountTargetPlaceholder": "Zadejte uživatelské jméno místního účtu, ke kterému chcete připojit", - "linkAccountOidcUsername": "Uživatelské jméno externího účtu", - "linkAccountOidcPlaceholder": "Zadejte uživatelské jméno externího účtu, který chcete sloučit", - "linkAccountOidcNotFound": "S tímto uživatelským jménem nebyl nalezen žádný externí účet.", + "linkAccountExternalUsername": "Uživatelské jméno externího účtu", + "linkAccountExternalPlaceholder": "Zadejte uživatelské jméno externího účtu, který chcete sloučit", + "linkAccountExternalNotFound": "S tímto uživatelským jménem nebyl nalezen žádný externí účet.", "linkAccounts": "Propojit účty", "linkAccountSuccess": "Účty byly úspěšně propojeny", "linkAccountFailed": "Propojení účtů se nezdařilo", @@ -1400,7 +1400,6 @@ "saving": "Ukládání...", "updateRegistrationFailed": "Nepodařilo se aktualizovat nastavení registrace", "updatePasswordLoginFailed": "Nepodařilo se aktualizovat nastavení přihlašovacího hesla", - "updateOidcAutoProvisionFailed": "Nepodařilo se aktualizovat nastavení automatického vytváření", "updateSecondFactorAfterExternalLoginFailed": "Nepodařilo se aktualizovat nastavení druhého faktoru", "updatePasswordResetFailed": "Nastavení resetování hesla se nepodařilo aktualizovat", "sessionTimeoutRange2": "Časový limit relace musí být mezi 1 a 720 hodinami.", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "ZAMČENO", "dataLockedNotice": "Data uživatele {{username}}zůstanou uzamčena až do jeho dalšího přihlášení. Do té doby nelze jeho hostitele, přihlašovací údaje a úryvky zobrazit ani upravovat.", "resetPasswordTitle": "Obnovit heslo", - "resetPasswordOidcOnly": "Tento uživatel se přihlašuje přes externího poskytovatele a nemá heslo.", + "resetPasswordExternalOnly": "Tento uživatel se přihlašuje přes externího poskytovatele a nemá heslo.", "resetPasswordPlaceholder": "Nové heslo", "resetPasswordBtn": "Obnovit", "resetPasswordWorking": "Resetování...", @@ -1742,7 +1741,7 @@ "importFailed": "Import se nezdařil: {{error}}", "roleUser": "Uživatel", "authMethodDual": "Duální ověřování", - "authMethodOidc": "Externí", + "authMethodExternal": "Externí", "done": "Hotovo", "apiKeyNameRequired": "Název klíče je povinný", "apiKeyCreated": "Klíč API „{{name}}“ byl vytvořen", diff --git a/src/ui/locales/translated/da_DK.json b/src/ui/locales/translated/da_DK.json index b5a38d095..6302d0b2a 100644 --- a/src/ui/locales/translated/da_DK.json +++ b/src/ui/locales/translated/da_DK.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "Tving tastaturinteraktivt", "overrideCredentialUsername": "Tilsidesæt brugernavn til legitimationsoplysninger", "overrideCredentialUsernameDesc": "Brug det ovenfor angivne brugernavn i stedet for legitimationsoplysningernes brugernavn", - "oidcUsernameHint": "Brug $external.username til at udfylde det navn, du loggede ind med via SSO eller LDAP.", + "externalUsernameHint": "Brug $external.username til at udfylde det navn, du loggede ind med via SSO eller LDAP.", "jumpHostChain": "Jump Host Chain", "portKnocking": "Port Knocking", "addKnock": "Tilføj port", @@ -977,7 +977,7 @@ "noSearchResults": "Ingen forbindelser matcher din søgning", "rename": "Omdøb session" }, - "terminal": { + "sshAuth": { "totpRequired": "Tofaktorgodkendelse påkrævet", "totpCodeLabel": "Bekræftelseskode", "totpVerify": "Verificere", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "Kunne ikke bekræfte nulstillingskoden", "failedCompleteReset": "Nulstilling af adgangskode mislykkedes", "invalidTotpCode": "Ugyldig TOTP-kode", - "failedOidcLogin": "Kunne ikke starte ekstern login", - "silentSigninOidcUnavailable": "Der blev anmodet om lydløs login, men der er ikke konfigureret ekstern login.", + "failedExternalLogin": "Kunne ikke starte ekstern login", + "silentSigninExternalUnavailable": "Der blev anmodet om lydløs login, men der er ikke konfigureret ekstern login.", "failedUserInfo": "Kunne ikke hente brugeroplysninger efter login", - "oidcAuthFailed": "Ekstern login mislykkedes", + "externalAuthFailed": "Ekstern login mislykkedes", "invalidAuthUrl": "Ugyldig godkendelses-URL modtaget fra backend", "requiredField": "Dette felt er påkrævet", "minLength": "Minimumslængden er {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "Lad nye brugere selv registrere sig med brugernavn og adgangskode", "allowPasswordLogin": "Tillad adgangskodelogin", "allowPasswordLoginDesc": "Brugernavn/adgangskode til login", - "oidcAutoProvision": "Opret eksterne konti automatisk", - "oidcAutoProvisionDesc": "Opret en konto første gang nogen logger ind via et eksternt login, f.eks. SSO eller LDAP, selv når registrering er slået fra.", + "externalAutoProvision": "Opret eksterne konti automatisk", + "externalAutoProvisionDesc": "Opret en konto første gang nogen logger ind via et eksternt login, f.eks. SSO eller LDAP, selv når registrering er slået fra.", "secondFactorAfterExternalLogin": "Bed om en anden faktor efter eksterne logins (SSO, GitHub, Google, LDAP)", "secondFactorAfterExternalLoginDesc": "Fra, en tilmeldt sekundær faktor bliver kun bedt om efter adgangskode og andre lokale logins, ligesom før. Til, bliver bedt om den efter hver loginmetode, inklusive SSO og LDAP.", "allowPasswordReset": "Tillad nulstilling af adgangskode", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "Ingen udløbsdato", "revokedBadge": "TILBAGETAGET", "authTypeDual": "Dobbeltgodkendelse", - "authTypeOidc": "Ekstern", + "authTypeExternal": "Ekstern", "authTypeLocal": "Lokal", "adminStatusAdministrator": "Administrator", "adminStatusRegularUser": "Almindelig bruger", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "Tillad både ekstern login og login med adgangskode", "linkAccountTargetUsername": "Brugernavn for lokal konto", "linkAccountTargetPlaceholder": "Indtast brugernavnet på den lokale konto, der skal linkes til", - "linkAccountOidcUsername": "Brugernavn til ekstern konto", - "linkAccountOidcPlaceholder": "Indtast brugernavnet til den eksterne konto, der skal flettes ind", - "linkAccountOidcNotFound": "Ingen ekstern konto fundet med det brugernavn", + "linkAccountExternalUsername": "Brugernavn til ekstern konto", + "linkAccountExternalPlaceholder": "Indtast brugernavnet til den eksterne konto, der skal flettes ind", + "linkAccountExternalNotFound": "Ingen ekstern konto fundet med det brugernavn", "linkAccounts": "Tilknyt konti", "linkAccountSuccess": "Konti er blevet linket", "linkAccountFailed": "Kunne ikke linke konti", @@ -1400,7 +1400,6 @@ "saving": "Gemmer...", "updateRegistrationFailed": "Kunne ikke opdatere registreringsindstillingerne", "updatePasswordLoginFailed": "Kunne ikke opdatere adgangskodeindstillingen for login", - "updateOidcAutoProvisionFailed": "Kunne ikke opdatere indstillingen for automatisk oprettelse", "updateSecondFactorAfterExternalLoginFailed": "Kunne ikke opdatere indstillingen for anden faktor", "updatePasswordResetFailed": "Indstillingen for nulstilling af adgangskode kunne ikke opdateres", "sessionTimeoutRange2": "Sessionstimeout skal være mellem 1 og 720 timer", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "LÅST", "dataLockedNotice": "{{username}}s data forbliver låst, indtil de logger ind næste gang. Deres værter, legitimationsoplysninger og kodestykker kan ikke ses eller redigeres indtil da.", "resetPasswordTitle": "Nulstil adgangskode", - "resetPasswordOidcOnly": "Denne bruger logger ind via en ekstern udbyder og har ingen adgangskode.", + "resetPasswordExternalOnly": "Denne bruger logger ind via en ekstern udbyder og har ingen adgangskode.", "resetPasswordPlaceholder": "Ny adgangskode", "resetPasswordBtn": "Nulstil", "resetPasswordWorking": "Nulstiller...", @@ -1742,7 +1741,7 @@ "importFailed": "Import mislykkedes: {{error}}", "roleUser": "Bruger", "authMethodDual": "Dobbeltgodkendelse", - "authMethodOidc": "Ekstern", + "authMethodExternal": "Ekstern", "done": "Færdig", "apiKeyNameRequired": "Nøglenavn er påkrævet", "apiKeyCreated": "API-nøglen \"{{name}}\" blev oprettet", diff --git a/src/ui/locales/translated/de_DE.json b/src/ui/locales/translated/de_DE.json index 2ffbc3b6c..486b71ebd 100644 --- a/src/ui/locales/translated/de_DE.json +++ b/src/ui/locales/translated/de_DE.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "Keyboard-Interactive erzwingen", "overrideCredentialUsername": "Benutzernamen der Anmeldeinformationen überschreiben", "overrideCredentialUsernameDesc": "Den oben angegebenen Benutzernamen anstelle des Benutzernamens der Anmeldeinformationen verwenden", - "oidcUsernameHint": "Verwenden Sie $external.username, um den Namen einzufügen, mit dem Sie sich über SSO oder LDAP angemeldet haben.", + "externalUsernameHint": "Verwenden Sie $external.username, um den Namen einzufügen, mit dem Sie sich über SSO oder LDAP angemeldet haben.", "jumpHostChain": "Jump-Host-Kette", "portKnocking": "Port-Knocking", "addKnock": "Port hinzufügen", @@ -977,7 +977,7 @@ "noSearchResults": "Keine Verbindungen entsprechen Ihrer Suche", "rename": "Sitzung umbenennen" }, - "terminal": { + "sshAuth": { "totpRequired": "Zwei-Faktor-Authentifizierung erforderlich", "totpCodeLabel": "Verifizierungscode", "totpVerify": "Verifizieren", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "Bestätigungscode konnte nicht überprüft werden", "failedCompleteReset": "Passwort-Zurücksetzung konnte nicht abgeschlossen werden", "invalidTotpCode": "Ungültiger TOTP-Code", - "failedOidcLogin": "Externe Anmeldung konnte nicht gestartet werden", - "silentSigninOidcUnavailable": "Es wurde eine stille Anmeldung angefordert, aber es ist keine externe Anmeldung eingerichtet.", + "failedExternalLogin": "Externe Anmeldung konnte nicht gestartet werden", + "silentSigninExternalUnavailable": "Es wurde eine stille Anmeldung angefordert, aber es ist keine externe Anmeldung eingerichtet.", "failedUserInfo": "Benutzerinfo nach Anmeldung konnte nicht abgerufen werden", - "oidcAuthFailed": "Externe Anmeldung fehlgeschlagen", + "externalAuthFailed": "Externe Anmeldung fehlgeschlagen", "invalidAuthUrl": "Ungültige Autorisierungs-URL vom Backend erhalten", "requiredField": "Dieses Feld ist erforderlich", "minLength": "Mindestlänge ist {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "Neuen Benutzern die Selbstregistrierung mit Benutzernamen und Passwort erlauben", "allowPasswordLogin": "Passwort-Login erlauben", "allowPasswordLoginDesc": "Login mit Benutzernamen/Passwort", - "oidcAutoProvision": "Automatische Erstellung externer Konten", - "oidcAutoProvisionDesc": "Erstellen Sie ein Konto, sobald sich jemand zum ersten Mal über einen externen Login wie SSO oder LDAP anmeldet, auch wenn die Registrierung deaktiviert ist.", + "externalAutoProvision": "Automatische Erstellung externer Konten", + "externalAutoProvisionDesc": "Erstellen Sie ein Konto, sobald sich jemand zum ersten Mal über einen externen Login wie SSO oder LDAP anmeldet, auch wenn die Registrierung deaktiviert ist.", "secondFactorAfterExternalLogin": "Fordern Sie nach externen Anmeldungen (SSO, GitHub, Google, LDAP) einen zweiten Faktor an.", "secondFactorAfterExternalLoginDesc": "Ist die Zwei-Faktor-Authentifizierung deaktiviert, wird sie wie bisher nur nach dem Passwort und anderen lokalen Anmeldungen abgefragt. Ist sie aktiviert, wird sie nach jeder Anmeldemethode abgefragt, einschließlich SSO und LDAP.", "allowPasswordReset": "Passwort-Zurücksetzen erlauben", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "Kein Ablauf", "revokedBadge": "WIDERRUFEN", "authTypeDual": "Duale Authentifizierung", - "authTypeOidc": "Extern", + "authTypeExternal": "Extern", "authTypeLocal": "Lokal", "adminStatusAdministrator": "Administrator", "adminStatusRegularUser": "Normaler Benutzer", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "Sowohl externe Anmeldung als auch Anmeldung mit Passwort zulassen", "linkAccountTargetUsername": "Benutzername des lokalen Kontos", "linkAccountTargetPlaceholder": "Geben Sie den Benutzernamen des lokalen Kontos ein, mit dem verknüpft werden soll", - "linkAccountOidcUsername": "Benutzername des externen Kontos", - "linkAccountOidcPlaceholder": "Geben Sie den Benutzernamen des externen Kontos ein, das zusammengeführt werden soll.", - "linkAccountOidcNotFound": "Es wurde kein externes Konto mit diesem Benutzernamen gefunden.", + "linkAccountExternalUsername": "Benutzername des externen Kontos", + "linkAccountExternalPlaceholder": "Geben Sie den Benutzernamen des externen Kontos ein, das zusammengeführt werden soll.", + "linkAccountExternalNotFound": "Es wurde kein externes Konto mit diesem Benutzernamen gefunden.", "linkAccounts": "Konten verknüpfen", "linkAccountSuccess": "Konten erfolgreich verknüpft", "linkAccountFailed": "Fehler beim Verknüpfen der Konten", @@ -1400,7 +1400,6 @@ "saving": "Speichern...", "updateRegistrationFailed": "Fehler beim Aktualisieren der Registrierungseinstellung", "updatePasswordLoginFailed": "Fehler beim Aktualisieren der Passwort-Anmeldeeinstellung", - "updateOidcAutoProvisionFailed": "Die Aktualisierung der Einstellung für die automatische Erstellung ist fehlgeschlagen.", "updateSecondFactorAfterExternalLoginFailed": "Zweiter-Faktor-Einstellung konnte nicht aktualisiert werden", "updatePasswordResetFailed": "Fehler beim Aktualisieren der Passwort-Zurücksetzen-Einstellung", "sessionTimeoutRange2": "Sitzungszeitüberschreitung muss zwischen 1 und 720 Stunden liegen", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "GESPERRT", "dataLockedNotice": "{{username}}s Daten bleiben gesperrt, bis die nächste Anmeldung erfolgt. Hosts, Zugangsdaten und Snippets können bis dahin nicht eingesehen oder bearbeitet werden.", "resetPasswordTitle": "Passwort zurücksetzen", - "resetPasswordOidcOnly": "Dieser Benutzer meldet sich über einen externen Anbieter an und hat kein Passwort.", + "resetPasswordExternalOnly": "Dieser Benutzer meldet sich über einen externen Anbieter an und hat kein Passwort.", "resetPasswordPlaceholder": "Neues Passwort", "resetPasswordBtn": "Zurücksetzen", "resetPasswordWorking": "Wird zurückgesetzt...", @@ -1742,7 +1741,7 @@ "importFailed": "Import fehlgeschlagen: {{error}}", "roleUser": "Benutzer", "authMethodDual": "Duale Authentifizierung", - "authMethodOidc": "Extern", + "authMethodExternal": "Extern", "done": "Fertig", "apiKeyNameRequired": "Schlüsselname ist erforderlich", "apiKeyCreated": "API-Schlüssel \"{{name}}\" erstellt", diff --git a/src/ui/locales/translated/el_GR.json b/src/ui/locales/translated/el_GR.json index 3a80339ad..90d332bae 100644 --- a/src/ui/locales/translated/el_GR.json +++ b/src/ui/locales/translated/el_GR.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "Force Keyboard-Interactive", "overrideCredentialUsername": "Παράκαμψη ονόματος χρήστη διαπιστευτηρίων", "overrideCredentialUsernameDesc": "Χρησιμοποιήστε το όνομα χρήστη που καθορίστηκε παραπάνω αντί για το όνομα χρήστη του πιστοποιητικού", - "oidcUsernameHint": "Χρησιμοποιήστε την εντολή $external.username για να συμπληρώσετε το όνομα με το οποίο συνδεθήκατε μέσω SSO ή LDAP.", + "externalUsernameHint": "Χρησιμοποιήστε την εντολή $external.username για να συμπληρώσετε το όνομα με το οποίο συνδεθήκατε μέσω SSO ή LDAP.", "jumpHostChain": "Αλυσίδα υποδοχής άλματος", "portKnocking": "Χτύπημα στο λιμάνι", "addKnock": "Προσθήκη θύρας", @@ -977,7 +977,7 @@ "noSearchResults": "Δεν υπάρχουν συνδέσεις που να αντιστοιχούν στην αναζήτησή σας", "rename": "Μετονομασία συνεδρίας" }, - "terminal": { + "sshAuth": { "totpRequired": "Απαιτείται έλεγχος ταυτότητας δύο παραγόντων", "totpCodeLabel": "Κωδικός επαλήθευσης", "totpVerify": "Επαληθεύω", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "Αποτυχία επαλήθευσης κωδικού επαναφοράς", "failedCompleteReset": "Η ολοκλήρωση της επαναφοράς του κωδικού πρόσβασης απέτυχε", "invalidTotpCode": "Μη έγκυρος κωδικός TOTP", - "failedOidcLogin": "Αποτυχία έναρξης εξωτερικής σύνδεσης", - "silentSigninOidcUnavailable": "Ζητήθηκε αθόρυβη σύνδεση, αλλά δεν έχει ρυθμιστεί εξωτερική σύνδεση.", + "failedExternalLogin": "Αποτυχία έναρξης εξωτερικής σύνδεσης", + "silentSigninExternalUnavailable": "Ζητήθηκε αθόρυβη σύνδεση, αλλά δεν έχει ρυθμιστεί εξωτερική σύνδεση.", "failedUserInfo": "Αποτυχία λήψης πληροφοριών χρήστη μετά τη σύνδεση", - "oidcAuthFailed": "Η εξωτερική σύνδεση απέτυχε", + "externalAuthFailed": "Η εξωτερική σύνδεση απέτυχε", "invalidAuthUrl": "Λήψη μη έγκυρης διεύθυνσης URL εξουσιοδότησης από το backend", "requiredField": "Αυτό το πεδίο είναι υποχρεωτικό", "minLength": "Το ελάχιστο μήκος είναι {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "Επιτρέψτε στους νέους χρήστες να εγγραφούν μόνοι τους με όνομα χρήστη και κωδικό πρόσβασης", "allowPasswordLogin": "Επιτρέψτε την είσοδο με κωδικό πρόσβασης", "allowPasswordLoginDesc": "Όνομα χρήστη/κωδικός πρόσβασης σύνδεσης", - "oidcAutoProvision": "Αυτόματη δημιουργία εξωτερικών λογαριασμών", - "oidcAutoProvisionDesc": "Δημιουργήστε έναν λογαριασμό την πρώτη φορά που κάποιος συνδέεται μέσω εξωτερικής σύνδεσης, όπως SSO ή LDAP, ακόμα και όταν η εγγραφή είναι απενεργοποιημένη.", + "externalAutoProvision": "Αυτόματη δημιουργία εξωτερικών λογαριασμών", + "externalAutoProvisionDesc": "Δημιουργήστε έναν λογαριασμό την πρώτη φορά που κάποιος συνδέεται μέσω εξωτερικής σύνδεσης, όπως SSO ή LDAP, ακόμα και όταν η εγγραφή είναι απενεργοποιημένη.", "secondFactorAfterExternalLogin": "Ζητήστε έναν δεύτερο παράγοντα μετά από εξωτερικές συνδέσεις (SSO, GitHub, Google, LDAP)", "secondFactorAfterExternalLoginDesc": "Όταν είναι απενεργοποιημένο, ζητείται ένας εγγεγραμμένος δεύτερος παράγοντας μόνο μετά τον κωδικό πρόσβασης και άλλες τοπικές συνδέσεις, όπως και πριν. Όταν είναι ενεργοποιημένο, ζητείται μετά από κάθε μέθοδο σύνδεσης, συμπεριλαμβανομένων των SSO και LDAP.", "allowPasswordReset": "Να επιτρέπεται η επαναφορά κωδικού πρόσβασης", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "Χωρίς λήξη", "revokedBadge": "ΑΝΑΚΛΗΘΗΚΕ", "authTypeDual": "Διπλή Εξουσιοδότηση", - "authTypeOidc": "Εξωτερικός", + "authTypeExternal": "Εξωτερικός", "authTypeLocal": "Τοπικός", "adminStatusAdministrator": "Διαχειριστής", "adminStatusRegularUser": "Τακτικός χρήστης", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "Να επιτρέπεται η εξωτερική σύνδεση και η σύνδεση με κωδικό πρόσβασης", "linkAccountTargetUsername": "Όνομα χρήστη τοπικού λογαριασμού", "linkAccountTargetPlaceholder": "Εισαγάγετε το όνομα χρήστη του τοπικού λογαριασμού για να συνδεθείτε", - "linkAccountOidcUsername": "Όνομα χρήστη εξωτερικού λογαριασμού", - "linkAccountOidcPlaceholder": "Εισαγάγετε το όνομα χρήστη που προορίζεται μόνο για εξωτερικούς λογαριασμούς για συγχώνευση", - "linkAccountOidcNotFound": "Δεν βρέθηκε λογαριασμός μόνο για εξωτερικούς χρήστες με αυτό το όνομα χρήστη", + "linkAccountExternalUsername": "Όνομα χρήστη εξωτερικού λογαριασμού", + "linkAccountExternalPlaceholder": "Εισαγάγετε το όνομα χρήστη που προορίζεται μόνο για εξωτερικούς λογαριασμούς για συγχώνευση", + "linkAccountExternalNotFound": "Δεν βρέθηκε λογαριασμός μόνο για εξωτερικούς χρήστες με αυτό το όνομα χρήστη", "linkAccounts": "Σύνδεση λογαριασμών", "linkAccountSuccess": "Οι λογαριασμοί συνδέθηκαν με επιτυχία", "linkAccountFailed": "Η σύνδεση λογαριασμών απέτυχε", @@ -1400,7 +1400,6 @@ "saving": "Οικονομία...", "updateRegistrationFailed": "Η ενημέρωση της ρύθμισης εγγραφής απέτυχε", "updatePasswordLoginFailed": "Αποτυχία ενημέρωσης ρύθμισης κωδικού πρόσβασης σύνδεσης", - "updateOidcAutoProvisionFailed": "Η ενημέρωση της ρύθμισης αυτόματης δημιουργίας απέτυχε", "updateSecondFactorAfterExternalLoginFailed": "Αποτυχία ενημέρωσης της ρύθμισης του δεύτερου παράγοντα", "updatePasswordResetFailed": "Η ενημέρωση της ρύθμισης επαναφοράς κωδικού πρόσβασης απέτυχε", "sessionTimeoutRange2": "Το χρονικό όριο λήξης της περιόδου σύνδεσης πρέπει να είναι μεταξύ 1 και 720 ωρών", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "ΚΛΕΙΔΩΜΕΝΟ", "dataLockedNotice": "Τα δεδομένα του χρήστη {{username}}παραμένουν κλειδωμένα μέχρι την επόμενη σύνδεσή τους. Οι κεντρικοί υπολογιστές, τα διαπιστευτήρια και τα αποσπάσματα δεδομένων τους δεν μπορούν να προβληθούν ή να επεξεργαστούν μέχρι τότε.", "resetPasswordTitle": "Επαναφορά κωδικού πρόσβασης", - "resetPasswordOidcOnly": "Αυτός ο χρήστης συνδέεται μέσω εξωτερικού παρόχου και δεν έχει κωδικό πρόσβασης.", + "resetPasswordExternalOnly": "Αυτός ο χρήστης συνδέεται μέσω εξωτερικού παρόχου και δεν έχει κωδικό πρόσβασης.", "resetPasswordPlaceholder": "Νέος κωδικός πρόσβασης", "resetPasswordBtn": "Επαναφορά", "resetPasswordWorking": "Επαναφορά...", @@ -1742,7 +1741,7 @@ "importFailed": "Η εισαγωγή απέτυχε: {{error}}", "roleUser": "Μεταχειριζόμενος", "authMethodDual": "Διπλή Εξουσιοδότηση", - "authMethodOidc": "Εξωτερικός", + "authMethodExternal": "Εξωτερικός", "done": "Γινώμενος", "apiKeyNameRequired": "Απαιτείται όνομα κλειδιού", "apiKeyCreated": "Κλειδί API \"{{name}}\" δημιουργήθηκε", diff --git a/src/ui/locales/translated/es_ES.json b/src/ui/locales/translated/es_ES.json index d9cdc780c..74b918341 100644 --- a/src/ui/locales/translated/es_ES.json +++ b/src/ui/locales/translated/es_ES.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "Forzar autenticación interactiva por teclado", "overrideCredentialUsername": "Anular nombre de usuario de la credencial", "overrideCredentialUsernameDesc": "Usar el nombre de usuario especificado arriba en lugar del nombre de usuario de la credencial", - "oidcUsernameHint": "Utilice $external.username para completar el nombre con el que inició sesión a través de SSO o LDAP.", + "externalUsernameHint": "Utilice $external.username para completar el nombre con el que inició sesión a través de SSO o LDAP.", "jumpHostChain": "Cadena de hosts de salto", "portKnocking": "Port Knocking", "addKnock": "Añadir puerto", @@ -977,7 +977,7 @@ "noSearchResults": "Ninguna conexión coincide con tu búsqueda", "rename": "Renombrar sesión" }, - "terminal": { + "sshAuth": { "totpRequired": "Autenticación de dos factores requerida", "totpCodeLabel": "Código de verificación", "totpVerify": "Verificar", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "Error al verificar el código de restablecimiento", "failedCompleteReset": "Error al completar el restablecimiento de contraseña", "invalidTotpCode": "Código TOTP no válido", - "failedOidcLogin": "No se pudo iniciar el inicio de sesión externo.", - "silentSigninOidcUnavailable": "Se solicitó el inicio de sesión silencioso, pero no hay ningún inicio de sesión externo configurado.", + "failedExternalLogin": "No se pudo iniciar el inicio de sesión externo.", + "silentSigninExternalUnavailable": "Se solicitó el inicio de sesión silencioso, pero no hay ningún inicio de sesión externo configurado.", "failedUserInfo": "Error al obtener la información del usuario después del inicio de sesión", - "oidcAuthFailed": "Falló el inicio de sesión externo", + "externalAuthFailed": "Falló el inicio de sesión externo", "invalidAuthUrl": "URL de autorización no válida recibida del backend", "requiredField": "Este campo es obligatorio", "minLength": "La longitud mínima es {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "Permite a los nuevos usuarios registrarse con un nombre de usuario y contraseña", "allowPasswordLogin": "Permitir inicio de sesión con contraseña", "allowPasswordLoginDesc": "Inicio de sesión con usuario/contraseña", - "oidcAutoProvision": "Crear automáticamente cuentas externas", - "oidcAutoProvisionDesc": "Crea una cuenta la primera vez que alguien inicie sesión a través de un inicio de sesión externo como SSO o LDAP, incluso cuando el registro esté desactivado.", + "externalAutoProvision": "Crear automáticamente cuentas externas", + "externalAutoProvisionDesc": "Crea una cuenta la primera vez que alguien inicie sesión a través de un inicio de sesión externo como SSO o LDAP, incluso cuando el registro esté desactivado.", "secondFactorAfterExternalLogin": "Solicita un segundo factor de autenticación después de los inicios de sesión externos (SSO, GitHub, Google, LDAP).", "secondFactorAfterExternalLoginDesc": "Desactivado, el segundo factor registrado solo se solicita después de la contraseña y otros inicios de sesión locales, igual que antes. Activado, se solicita después de cada método de inicio de sesión, incluidos SSO y LDAP.", "allowPasswordReset": "Permitir restablecimiento de contraseña", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "Sin caducidad", "revokedBadge": "REVOCADA", "authTypeDual": "Autenticación dual", - "authTypeOidc": "Externo", + "authTypeExternal": "Externo", "authTypeLocal": "Local", "adminStatusAdministrator": "Administrador", "adminStatusRegularUser": "Usuario normal", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "Permitir tanto el inicio de sesión externo como el inicio de sesión mediante contraseña.", "linkAccountTargetUsername": "Nombre de usuario de la cuenta local", "linkAccountTargetPlaceholder": "Introduzca el nombre de usuario de la cuenta local a vincular", - "linkAccountOidcUsername": "Nombre de usuario de cuenta externa", - "linkAccountOidcPlaceholder": "Ingrese el nombre de usuario de la cuenta externa para fusionarla", - "linkAccountOidcNotFound": "No se encontró ninguna cuenta externa con ese nombre de usuario.", + "linkAccountExternalUsername": "Nombre de usuario de cuenta externa", + "linkAccountExternalPlaceholder": "Ingrese el nombre de usuario de la cuenta externa para fusionarla", + "linkAccountExternalNotFound": "No se encontró ninguna cuenta externa con ese nombre de usuario.", "linkAccounts": "Vincular cuentas", "linkAccountSuccess": "Cuentas vinculadas correctamente", "linkAccountFailed": "Error al vincular cuentas", @@ -1400,7 +1400,6 @@ "saving": "Guardando...", "updateRegistrationFailed": "Error al actualizar la configuración de registro", "updatePasswordLoginFailed": "Error al actualizar la configuración de inicio de sesión por contraseña", - "updateOidcAutoProvisionFailed": "No se pudo actualizar la configuración de creación automática.", "updateSecondFactorAfterExternalLoginFailed": "No se pudo actualizar la configuración del segundo factor.", "updatePasswordResetFailed": "Error al actualizar la configuración de restablecimiento de contraseña", "sessionTimeoutRange2": "El tiempo de espera de sesión debe estar entre 1 y 720 horas", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "BLOQUEADO", "dataLockedNotice": "Los datos de {{username}} permanecen bloqueados hasta que inicien sesión de nuevo. Sus hosts, credenciales y fragmentos no se pueden ver ni editar hasta entonces.", "resetPasswordTitle": "Restablecer contraseña", - "resetPasswordOidcOnly": "Este usuario inicia sesión a través de un proveedor externo y no tiene contraseña.", + "resetPasswordExternalOnly": "Este usuario inicia sesión a través de un proveedor externo y no tiene contraseña.", "resetPasswordPlaceholder": "Nueva contraseña", "resetPasswordBtn": "Restablecer", "resetPasswordWorking": "Restableciendo...", @@ -1742,7 +1741,7 @@ "importFailed": "Error al importar: {{error}}", "roleUser": "Usuario", "authMethodDual": "Autenticación dual", - "authMethodOidc": "Externo", + "authMethodExternal": "Externo", "done": "Listo", "apiKeyNameRequired": "El nombre de la clave es obligatorio", "apiKeyCreated": "Clave API \"{{name}}\" creada", diff --git a/src/ui/locales/translated/fi_FI.json b/src/ui/locales/translated/fi_FI.json index 168c24dd1..6b32a8f8e 100644 --- a/src/ui/locales/translated/fi_FI.json +++ b/src/ui/locales/translated/fi_FI.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "Pakota näppäimistöinteraktiivinen", "overrideCredentialUsername": "Ohita tunnistetiedot Käyttäjätunnus", "overrideCredentialUsernameDesc": "Käytä yllä määritettyä käyttäjätunnusta tunnistetiedon käyttäjätunnuksen sijaan.", - "oidcUsernameHint": "Käytä muotoa $external.username täyttääksesi nimen, jolla kirjauduit sisään SSO:n tai LDAP:n kautta.", + "externalUsernameHint": "Käytä muotoa $external.username täyttääksesi nimen, jolla kirjauduit sisään SSO:n tai LDAP:n kautta.", "jumpHostChain": "Hyppyisäntäketju", "portKnocking": "Portin koputus", "addKnock": "Lisää portti", @@ -977,7 +977,7 @@ "noSearchResults": "Ei hakuasi vastaavia yhteyksiä", "rename": "Nimeä istunto uudelleen" }, - "terminal": { + "sshAuth": { "totpRequired": "Kaksivaiheinen todennus vaaditaan", "totpCodeLabel": "Vahvistuskoodi", "totpVerify": "Vahvista", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "Nollauskoodin vahvistaminen epäonnistui", "failedCompleteReset": "Salasanan vaihtaminen epäonnistui", "invalidTotpCode": "Virheellinen TOTP-koodi", - "failedOidcLogin": "Ulkoisen kirjautumisen aloittaminen epäonnistui", - "silentSigninOidcUnavailable": "Hiljaista kirjautumista pyydettiin, mutta ulkoista kirjautumista ei ole määritetty.", + "failedExternalLogin": "Ulkoisen kirjautumisen aloittaminen epäonnistui", + "silentSigninExternalUnavailable": "Hiljaista kirjautumista pyydettiin, mutta ulkoista kirjautumista ei ole määritetty.", "failedUserInfo": "Käyttäjätietojen hakeminen epäonnistui kirjautumisen jälkeen", - "oidcAuthFailed": "Ulkoinen kirjautuminen epäonnistui", + "externalAuthFailed": "Ulkoinen kirjautuminen epäonnistui", "invalidAuthUrl": "Virheellinen valtuutus-URL-osoite vastaanotettu taustajärjestelmästä", "requiredField": "Tämä kenttä on pakollinen", "minLength": "Minimipituus on {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "Anna uusien käyttäjien rekisteröityä itse käyttäjätunnuksella ja salasanalla", "allowPasswordLogin": "Salli salasanalla kirjautuminen", "allowPasswordLoginDesc": "Käyttäjätunnus/salasana kirjautuminen", - "oidcAutoProvision": "Luo ulkoiset tilit automaattisesti", - "oidcAutoProvisionDesc": "Luo tili, kun joku kirjautuu sisään ensimmäisen kerran ulkoisella kirjautumisella, kuten SSO:lla tai LDAP:lla, vaikka rekisteröinti olisi pois päältä.", + "externalAutoProvision": "Luo ulkoiset tilit automaattisesti", + "externalAutoProvisionDesc": "Luo tili, kun joku kirjautuu sisään ensimmäisen kerran ulkoisella kirjautumisella, kuten SSO:lla tai LDAP:lla, vaikka rekisteröinti olisi pois päältä.", "secondFactorAfterExternalLogin": "Kysy toista kirjautumistekijää ulkoisten kirjautumisten (SSO, GitHub, Google, LDAP) jälkeen", "secondFactorAfterExternalLoginDesc": "Pois päältä -tilassa rekisteröityä toista kirjautumistapaa kysytään vain salasanan ja muiden paikallisten kirjautumisten jälkeen, kuten aiemmin. Päällä -tilassa sitä kysytään jokaisen kirjautumistavan, mukaan lukien kertakirjautumisen ja LDAP:n, jälkeen.", "allowPasswordReset": "Salli salasanan palautus", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "Ei vanhenemista", "revokedBadge": "PERUTTU", "authTypeDual": "Kaksoistutkimus", - "authTypeOidc": "Ulkoinen", + "authTypeExternal": "Ulkoinen", "authTypeLocal": "Paikallinen", "adminStatusAdministrator": "Ylläpitäjä", "adminStatusRegularUser": "Tavallinen käyttäjä", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "Salli sekä ulkoinen että salasanalla kirjautuminen", "linkAccountTargetUsername": "Paikallisen tilin käyttäjätunnus", "linkAccountTargetPlaceholder": "Anna paikallisen tilin käyttäjätunnus, johon linkitetään", - "linkAccountOidcUsername": "Ulkoisen tilin käyttäjätunnus", - "linkAccountOidcPlaceholder": "Anna yhdistettävän ulkoisen tilin käyttäjätunnus", - "linkAccountOidcNotFound": "Käyttäjätunnuksella ei löytynyt ulkoista tiliä.", + "linkAccountExternalUsername": "Ulkoisen tilin käyttäjätunnus", + "linkAccountExternalPlaceholder": "Anna yhdistettävän ulkoisen tilin käyttäjätunnus", + "linkAccountExternalNotFound": "Käyttäjätunnuksella ei löytynyt ulkoista tiliä.", "linkAccounts": "Linkitä tilit", "linkAccountSuccess": "Tilit linkitetty onnistuneesti", "linkAccountFailed": "Tilien linkittäminen epäonnistui", @@ -1400,7 +1400,6 @@ "saving": "Tallennetaan...", "updateRegistrationFailed": "Rekisteröintiasetuksen päivittäminen epäonnistui", "updatePasswordLoginFailed": "Salasanan kirjautumisasetusten päivittäminen epäonnistui", - "updateOidcAutoProvisionFailed": "Automaattisen luomisen asetuksen päivittäminen epäonnistui", "updateSecondFactorAfterExternalLoginFailed": "Toisen vaiheen asetuksen päivittäminen epäonnistui", "updatePasswordResetFailed": "Salasanan palautusasetuksen päivittäminen epäonnistui", "sessionTimeoutRange2": "Istunnon aikakatkaisun on oltava 1–720 tuntia", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "LUKITTU", "dataLockedNotice": "{{username}}:n tiedot pysyvät lukittuina, kunnes he kirjautuvat sisään seuraavalla kerralla. Heidän palvelimiaan, tunnistetietojaan ja koodinpätkiään ei voida tarkastella tai muokata ennen sitä.", "resetPasswordTitle": "Palauta salasana", - "resetPasswordOidcOnly": "Tämä käyttäjä kirjautuu sisään ulkoisen palveluntarjoajan kautta eikä hänellä ole salasanaa.", + "resetPasswordExternalOnly": "Tämä käyttäjä kirjautuu sisään ulkoisen palveluntarjoajan kautta eikä hänellä ole salasanaa.", "resetPasswordPlaceholder": "Uusi salasana", "resetPasswordBtn": "Nollaa", "resetPasswordWorking": "Nollataan...", @@ -1742,7 +1741,7 @@ "importFailed": "Tuonti epäonnistui: {{error}}", "roleUser": "Käyttäjä", "authMethodDual": "Kaksoistutkimus", - "authMethodOidc": "Ulkoinen", + "authMethodExternal": "Ulkoinen", "done": "Tehty", "apiKeyNameRequired": "Avaimen nimi on pakollinen", "apiKeyCreated": "API-avain \"{{name}}\" luotu", diff --git a/src/ui/locales/translated/fr_FR.json b/src/ui/locales/translated/fr_FR.json index 2c02692a8..371b90dc6 100644 --- a/src/ui/locales/translated/fr_FR.json +++ b/src/ui/locales/translated/fr_FR.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "Forcer Keyboard-Interactive", "overrideCredentialUsername": "Remplacer le nom d'utilisateur des identifiants", "overrideCredentialUsernameDesc": "Utiliser le nom d'utilisateur spécifié ci-dessus au lieu de celui des identifiants", - "oidcUsernameHint": "Utilisez $external.username pour renseigner le nom avec lequel vous vous êtes connecté via SSO ou LDAP.", + "externalUsernameHint": "Utilisez $external.username pour renseigner le nom avec lequel vous vous êtes connecté via SSO ou LDAP.", "jumpHostChain": "Chaîne de Jump Hosts", "portKnocking": "Port Knocking", "addKnock": "Ajouter un port", @@ -977,7 +977,7 @@ "noSearchResults": "Aucune connexion ne correspond à votre recherche", "rename": "Renommer la session" }, - "terminal": { + "sshAuth": { "totpRequired": "Authentification à deux facteurs requise", "totpCodeLabel": "Code de vérification", "totpVerify": "Vérifier", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "Échec de la vérification du code de réinitialisation", "failedCompleteReset": "Échec de la finalisation de la réinitialisation du mot de passe", "invalidTotpCode": "Code TOTP invalide", - "failedOidcLogin": "Échec de la connexion externe", - "silentSigninOidcUnavailable": "Une connexion silencieuse a été demandée, mais aucune connexion externe n'est configurée.", + "failedExternalLogin": "Échec de la connexion externe", + "silentSigninExternalUnavailable": "Une connexion silencieuse a été demandée, mais aucune connexion externe n'est configurée.", "failedUserInfo": "Échec de la récupération des informations utilisateur après la connexion", - "oidcAuthFailed": "Échec de la connexion externe", + "externalAuthFailed": "Échec de la connexion externe", "invalidAuthUrl": "URL d'autorisation invalide reçue du backend", "requiredField": "Ce champ est obligatoire", "minLength": "La longueur minimale est de {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "Permettre aux nouveaux utilisateurs de s'enregistrer avec un nom d'utilisateur et un mot de passe", "allowPasswordLogin": "Autoriser la connexion par mot de passe", "allowPasswordLoginDesc": "Connexion par nom d'utilisateur/mot de passe", - "oidcAutoProvision": "Création automatique de comptes externes", - "oidcAutoProvisionDesc": "Créer un compte lors de la première connexion via une authentification externe telle que SSO ou LDAP, même si l'inscription est désactivée.", + "externalAutoProvision": "Création automatique de comptes externes", + "externalAutoProvisionDesc": "Créer un compte lors de la première connexion via une authentification externe telle que SSO ou LDAP, même si l'inscription est désactivée.", "secondFactorAfterExternalLogin": "Demander une authentification à deux facteurs après les connexions externes (SSO, GitHub, Google, LDAP).", "secondFactorAfterExternalLoginDesc": "Désactivé, le second facteur d'authentification n'est demandé qu'après la saisie du mot de passe et les autres connexions locales, comme auparavant. Activé, il est demandé après chaque méthode de connexion, y compris l'authentification unique (SSO) et LDAP.", "allowPasswordReset": "Autoriser la réinitialisation du mot de passe", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "Aucune expiration", "revokedBadge": "RÉVOQUÉE", "authTypeDual": "Authentification double", - "authTypeOidc": "Externe", + "authTypeExternal": "Externe", "authTypeLocal": "Locale", "adminStatusAdministrator": "Administrateur", "adminStatusRegularUser": "Utilisateur standard", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "Autoriser la connexion externe et par mot de passe", "linkAccountTargetUsername": "Nom d'utilisateur du compte local", "linkAccountTargetPlaceholder": "Saisissez le nom d'utilisateur du compte local à lier", - "linkAccountOidcUsername": "Nom d'utilisateur du compte externe", - "linkAccountOidcPlaceholder": "Saisissez le nom d'utilisateur du compte externe uniquement à fusionner.", - "linkAccountOidcNotFound": "Aucun compte externe n'a été trouvé avec ce nom d'utilisateur.", + "linkAccountExternalUsername": "Nom d'utilisateur du compte externe", + "linkAccountExternalPlaceholder": "Saisissez le nom d'utilisateur du compte externe uniquement à fusionner.", + "linkAccountExternalNotFound": "Aucun compte externe n'a été trouvé avec ce nom d'utilisateur.", "linkAccounts": "Lier les comptes", "linkAccountSuccess": "Comptes liés avec succès", "linkAccountFailed": "Échec de la liaison des comptes", @@ -1400,7 +1400,6 @@ "saving": "Enregistrement…", "updateRegistrationFailed": "Échec de la mise à jour du paramètre d'enregistrement", "updatePasswordLoginFailed": "Échec de la mise à jour du paramètre de connexion par mot de passe", - "updateOidcAutoProvisionFailed": "Échec de la mise à jour du paramètre de création automatique", "updateSecondFactorAfterExternalLoginFailed": "Échec de la mise à jour du paramètre de deuxième facteur", "updatePasswordResetFailed": "Échec de la mise à jour du paramètre de réinitialisation du mot de passe", "sessionTimeoutRange2": "Le délai d'expiration de session doit être compris entre 1 et 720 heures", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "VERROUILLÉ", "dataLockedNotice": "Les données de {{username}} restent verrouillées jusqu'à leur prochaine connexion. Leurs hôtes, identifiants et extraits ne peuvent être consultés ni modifiés d'ici là.", "resetPasswordTitle": "Réinitialiser le mot de passe", - "resetPasswordOidcOnly": "Cet utilisateur se connecte via un fournisseur externe et n'a pas de mot de passe.", + "resetPasswordExternalOnly": "Cet utilisateur se connecte via un fournisseur externe et n'a pas de mot de passe.", "resetPasswordPlaceholder": "Nouveau mot de passe", "resetPasswordBtn": "Réinitialiser", "resetPasswordWorking": "Réinitialisation en cours...", @@ -1742,7 +1741,7 @@ "importFailed": "Échec de l'importation : {{error}}", "roleUser": "Utilisateur", "authMethodDual": "Authentification double", - "authMethodOidc": "Externe", + "authMethodExternal": "Externe", "done": "Terminé", "apiKeyNameRequired": "Le nom de la clé est requis", "apiKeyCreated": "Clé API \"{{name}}\" créée", diff --git a/src/ui/locales/translated/he_IL.json b/src/ui/locales/translated/he_IL.json index b55f20a61..765e6ce96 100644 --- a/src/ui/locales/translated/he_IL.json +++ b/src/ui/locales/translated/he_IL.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "כפיית מקלדת אינטראקטיבית", "overrideCredentialUsername": "עקיפת שם משתמש של פרטי כניסה", "overrideCredentialUsernameDesc": "השתמש בשם המשתמש שצוין למעלה במקום שם המשתמש של פרטי האישור", - "oidcUsernameHint": "השתמשו ב- $external.username כדי למלא את השם שאיתו נכנסתם דרך SSO או LDAP.", + "externalUsernameHint": "השתמשו ב- $external.username כדי למלא את השם שאיתו נכנסתם דרך SSO או LDAP.", "jumpHostChain": "שרשרת מארח קפיצה", "portKnocking": "פורט נוקינג", "addKnock": "הוסף יציאה", @@ -977,7 +977,7 @@ "noSearchResults": "אין קשרים התואמים את החיפוש שלך", "rename": "שינוי שם של סשן" }, - "terminal": { + "sshAuth": { "totpRequired": "נדרש אימות דו-שלבי", "totpCodeLabel": "קוד אימות", "totpVerify": "לְאַמֵת", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "נכשל אימות קוד האיפוס", "failedCompleteReset": "נכשל השלמת איפוס הסיסמה", "invalidTotpCode": "קוד TOTP לא חוקי", - "failedOidcLogin": "נכשלה התחלת הכניסה החיצונית", - "silentSigninOidcUnavailable": "התבקשה כניסה שקטה, אך לא הוגדרה כניסה חיצונית.", + "failedExternalLogin": "נכשלה התחלת הכניסה החיצונית", + "silentSigninExternalUnavailable": "התבקשה כניסה שקטה, אך לא הוגדרה כניסה חיצונית.", "failedUserInfo": "נכשלה קבלת פרטי המשתמש לאחר הכניסה", - "oidcAuthFailed": "הכניסה החיצונית נכשלה", + "externalAuthFailed": "הכניסה החיצונית נכשלה", "invalidAuthUrl": "כתובת URL לא חוקית להרשאה התקבלה מה-backend", "requiredField": "שדה זה נדרש", "minLength": "אורך מינימלי הוא {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "לאפשר למשתמשים חדשים להירשם באופן עצמאי באמצעות שם משתמש וסיסמה", "allowPasswordLogin": "אפשר כניסה עם סיסמה", "allowPasswordLoginDesc": "שם משתמש/סיסמה להתחברות", - "oidcAutoProvision": "יצירה אוטומטית של חשבונות חיצוניים", - "oidcAutoProvisionDesc": "צור חשבון בפעם הראשונה שמישהו מתחבר דרך התחברות חיצונית כגון SSO או LDAP, גם כאשר ההרשמה כבויה", + "externalAutoProvision": "יצירה אוטומטית של חשבונות חיצוניים", + "externalAutoProvisionDesc": "צור חשבון בפעם הראשונה שמישהו מתחבר דרך התחברות חיצונית כגון SSO או LDAP, גם כאשר ההרשמה כבויה", "secondFactorAfterExternalLogin": "בקשת גורם שני לאחר כניסות חיצוניות (SSO, GitHub, Google, LDAP)", "secondFactorAfterExternalLoginDesc": "כבוי, גורם שני רשום מתבקש רק לאחר סיסמה וכניסות מקומיות אחרות, כמו קודם. פעיל, הוא מתבקש לאחר כל שיטת כניסה, כולל SSO ו-LDAP.", "allowPasswordReset": "אפשר איפוס סיסמה", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "אין תפוגה", "revokedBadge": "בוטל", "authTypeDual": "אימות כפול", - "authTypeOidc": "חִיצוֹנִי", + "authTypeExternal": "חִיצוֹנִי", "authTypeLocal": "מְקוֹמִי", "adminStatusAdministrator": "מְנַהֵל", "adminStatusRegularUser": "משתמש רגיל", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "אפשר כניסה חיצונית וכניסה באמצעות סיסמה", "linkAccountTargetUsername": "שם משתמש של חשבון מקומי", "linkAccountTargetPlaceholder": "הזן את שם המשתמש של החשבון המקומי שאליו ברצונך לקשר", - "linkAccountOidcUsername": "שם משתמש של חשבון חיצוני", - "linkAccountOidcPlaceholder": "הזן את שם המשתמש של החשבון החיצוני בלבד כדי למזג אותו", - "linkAccountOidcNotFound": "לא נמצא חשבון חיצוני בלבד עם שם משתמש זה", + "linkAccountExternalUsername": "שם משתמש של חשבון חיצוני", + "linkAccountExternalPlaceholder": "הזן את שם המשתמש של החשבון החיצוני בלבד כדי למזג אותו", + "linkAccountExternalNotFound": "לא נמצא חשבון חיצוני בלבד עם שם משתמש זה", "linkAccounts": "קישור חשבונות", "linkAccountSuccess": "חשבונות קושרו בהצלחה", "linkAccountFailed": "קישור החשבונות נכשל", @@ -1400,7 +1400,6 @@ "saving": "חִסָכוֹן...", "updateRegistrationFailed": "עדכון הגדרות הרישום נכשל", "updatePasswordLoginFailed": "נכשל עדכון הגדרות הכניסה באמצעות סיסמה", - "updateOidcAutoProvisionFailed": "נכשל עדכון הגדרת היצירה האוטומטית", "updateSecondFactorAfterExternalLoginFailed": "נכשל עדכון הגדרת הגורם השני", "updatePasswordResetFailed": "נכשל עדכון הגדרת איפוס הסיסמה", "sessionTimeoutRange2": "זמן הקצוב לסשן חייב להיות בין שעה ל-720 שעות", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "נָעוּל", "dataLockedNotice": "הנתונים של {{username}}נשארים נעולים עד לכניסה הבאה שלהם. לא ניתן יהיה לצפות או לערוך את המארחים, האישורים והקטעים שלהם עד אז.", "resetPasswordTitle": "איפוס סיסמה", - "resetPasswordOidcOnly": "משתמש זה מתחבר דרך ספק חיצוני ואין לו סיסמה.", + "resetPasswordExternalOnly": "משתמש זה מתחבר דרך ספק חיצוני ואין לו סיסמה.", "resetPasswordPlaceholder": "סיסמה חדשה", "resetPasswordBtn": "אִתחוּל", "resetPasswordWorking": "איפוס...", @@ -1742,7 +1741,7 @@ "importFailed": "ייבוא נכשל: {{error}}", "roleUser": "מִשׁתַמֵשׁ", "authMethodDual": "אימות כפול", - "authMethodOidc": "חִיצוֹנִי", + "authMethodExternal": "חִיצוֹנִי", "done": "נַעֲשָׂה", "apiKeyNameRequired": "נדרש שם מפתח", "apiKeyCreated": "מפתח ה-API \"{{name}}\" נוצר", diff --git a/src/ui/locales/translated/hi_IN.json b/src/ui/locales/translated/hi_IN.json index fd1a85779..a473abcee 100644 --- a/src/ui/locales/translated/hi_IN.json +++ b/src/ui/locales/translated/hi_IN.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "कीबोर्ड-इंटरैक्टिव बाध्य करें", "overrideCredentialUsername": "क्रेडेंशियल यूज़रनेम ओवरराइड करें", "overrideCredentialUsernameDesc": "क्रेडेंशियल के यूज़रनेम के बजाय ऊपर दिए गए यूज़रनेम का उपयोग करें", - "oidcUsernameHint": "SSO या LDAP के माध्यम से साइन इन करने के लिए आपने जिस नाम का उपयोग किया है, उसे भरने के लिए $external.username का उपयोग करें।", + "externalUsernameHint": "SSO या LDAP के माध्यम से साइन इन करने के लिए आपने जिस नाम का उपयोग किया है, उसे भरने के लिए $external.username का उपयोग करें।", "jumpHostChain": "जंप होस्ट श्रृंखला", "portKnocking": "पोर्ट नॉकिंग", "addKnock": "पोर्ट जोड़ें", @@ -977,7 +977,7 @@ "noSearchResults": "आपकी खोज से मेल खाने वाला कोई कनेक्शन नहीं", "rename": "सत्र का नाम बदलें" }, - "terminal": { + "sshAuth": { "totpRequired": "दो-कारक प्रमाणीकरण आवश्यक है", "totpCodeLabel": "सत्यापन कोड", "totpVerify": "सत्यापित करें", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "रीसेट कोड सत्यापित करने में विफल", "failedCompleteReset": "पासवर्ड रीसेट पूरा करने में विफल", "invalidTotpCode": "अमान्य TOTP कोड", - "failedOidcLogin": "बाहरी साइन-इन शुरू करने में विफल", - "silentSigninOidcUnavailable": "साइलेंट साइन-इन का अनुरोध किया गया था, लेकिन कोई बाहरी साइन-इन सेट अप नहीं किया गया है।", + "failedExternalLogin": "बाहरी साइन-इन शुरू करने में विफल", + "silentSigninExternalUnavailable": "साइलेंट साइन-इन का अनुरोध किया गया था, लेकिन कोई बाहरी साइन-इन सेट अप नहीं किया गया है।", "failedUserInfo": "लॉगिन के बाद उपयोगकर्ता जानकारी प्राप्त करने में विफल", - "oidcAuthFailed": "बाहरी साइन-इन विफल रहा", + "externalAuthFailed": "बाहरी साइन-इन विफल रहा", "invalidAuthUrl": "बैकएंड से अमान्य प्राधिकरण URL प्राप्त हुआ", "requiredField": "यह फ़ील्ड आवश्यक है", "minLength": "न्यूनतम लंबाई {{min}} है", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "नए उपयोगकर्ताओं को उपयोगकर्ता नाम और पासवर्ड के साथ स्वयं पंजीकरण करने दें", "allowPasswordLogin": "पासवर्ड लॉगिन की अनुमति दें", "allowPasswordLoginDesc": "उपयोगकर्ता नाम/पासवर्ड लॉगिन", - "oidcAutoProvision": "बाह्य खाते स्वतः बनाएँ", - "oidcAutoProvisionDesc": "जब कोई व्यक्ति एसएसओ या एलडीएपी जैसे बाहरी लॉगिन के माध्यम से पहली बार साइन इन करता है, तो पंजीकरण बंद होने पर भी एक खाता बनाएं।", + "externalAutoProvision": "बाह्य खाते स्वतः बनाएँ", + "externalAutoProvisionDesc": "जब कोई व्यक्ति एसएसओ या एलडीएपी जैसे बाहरी लॉगिन के माध्यम से पहली बार साइन इन करता है, तो पंजीकरण बंद होने पर भी एक खाता बनाएं।", "secondFactorAfterExternalLogin": "बाहरी लॉगिन (एसएसओ, गिटहब, गूगल, एलडीएपी) के बाद दूसरे फैक्टर की मांग करें।", "secondFactorAfterExternalLoginDesc": "बंद होने पर, पासवर्ड और अन्य स्थानीय लॉगिन के बाद ही पंजीकृत द्वितीय कारक प्रमाणीकरण मांगा जाता है, जैसा कि पहले होता था। चालू होने पर, एसएसओ और एलडीएपी सहित प्रत्येक लॉगिन विधि के बाद इसे मांगा जाता है।", "allowPasswordReset": "पासवर्ड रीसेट की अनुमति दें", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "कोई समाप्ति नहीं", "revokedBadge": "रद्द", "authTypeDual": "दोहरा प्रमाणीकरण", - "authTypeOidc": "बाहरी", + "authTypeExternal": "बाहरी", "authTypeLocal": "स्थानीय", "adminStatusAdministrator": "प्रशासक", "adminStatusRegularUser": "सामान्य उपयोगकर्ता", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "बाहरी और पासवर्ड दोनों तरह के साइन-इन की अनुमति दें", "linkAccountTargetUsername": "स्थानीय खाते का उपयोगकर्ता नाम", "linkAccountTargetPlaceholder": "लिंक करने के लिए स्थानीय खाते का उपयोगकर्ता नाम दर्ज करें", - "linkAccountOidcUsername": "बाह्य खाता उपयोगकर्ता नाम", - "linkAccountOidcPlaceholder": "विलय करने के लिए केवल बाहरी खाते का उपयोगकर्ता नाम दर्ज करें", - "linkAccountOidcNotFound": "उस उपयोगकर्ता नाम से कोई बाहरी खाता नहीं मिला।", + "linkAccountExternalUsername": "बाह्य खाता उपयोगकर्ता नाम", + "linkAccountExternalPlaceholder": "विलय करने के लिए केवल बाहरी खाते का उपयोगकर्ता नाम दर्ज करें", + "linkAccountExternalNotFound": "उस उपयोगकर्ता नाम से कोई बाहरी खाता नहीं मिला।", "linkAccounts": "खाते लिंक करें", "linkAccountSuccess": "खाते सफलतापूर्वक लिंक किए गए", "linkAccountFailed": "खाते लिंक करने में विफल", @@ -1400,7 +1400,6 @@ "saving": "सहेजा जा रहा है...", "updateRegistrationFailed": "पंजीकरण सेटिंग अपडेट करने में विफल", "updatePasswordLoginFailed": "पासवर्ड लॉगिन सेटिंग अपडेट करने में विफल", - "updateOidcAutoProvisionFailed": "ऑटो-क्रिएट सेटिंग को अपडेट करने में विफलता", "updateSecondFactorAfterExternalLoginFailed": "द्वितीय कारक सेटिंग को अपडेट करने में विफल", "updatePasswordResetFailed": "पासवर्ड रीसेट सेटिंग अपडेट करने में विफल", "sessionTimeoutRange2": "सत्र समय-सीमा 1 से 720 घंटों के बीच होनी चाहिए", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "लॉक्ड", "dataLockedNotice": "{{username}} का डेटा तब तक लॉक रहता है जब तक वे अगली बार लॉग इन नहीं कर लेते। तब तक उनके होस्ट, क्रेडेंशियल और स्निपेट को न तो देखा जा सकता है और न ही संपादित किया जा सकता है।", "resetPasswordTitle": "पासवर्ड रीसेट करें", - "resetPasswordOidcOnly": "यह उपयोगकर्ता किसी बाहरी प्रदाता के माध्यम से साइन इन करता है और उसका कोई पासवर्ड नहीं है।", + "resetPasswordExternalOnly": "यह उपयोगकर्ता किसी बाहरी प्रदाता के माध्यम से साइन इन करता है और उसका कोई पासवर्ड नहीं है।", "resetPasswordPlaceholder": "नया पासवर्ड", "resetPasswordBtn": "रीसेट", "resetPasswordWorking": "रीसेट हो रहा है...", @@ -1742,7 +1741,7 @@ "importFailed": "आयात विफल: {{error}}", "roleUser": "उपयोगकर्ता", "authMethodDual": "दोहरा प्रमाणीकरण", - "authMethodOidc": "बाहरी", + "authMethodExternal": "बाहरी", "done": "पूर्ण", "apiKeyNameRequired": "कुंजी नाम आवश्यक है", "apiKeyCreated": "API कुंजी \"{{name}}\" बनाई गई", diff --git a/src/ui/locales/translated/hu_HU.json b/src/ui/locales/translated/hu_HU.json index 29aba949a..3d9ae52cb 100644 --- a/src/ui/locales/translated/hu_HU.json +++ b/src/ui/locales/translated/hu_HU.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "Kényszerítse a billentyűzet interaktivitását", "overrideCredentialUsername": "Hitelesítő adat felülírása Felhasználónév", "overrideCredentialUsernameDesc": "Használja a fent megadott felhasználónevet a hitelesítő adat felhasználóneve helyett.", - "oidcUsernameHint": "A $external.username paraméterrel töltse ki azt a nevet, amellyel SSO-n vagy LDAP-on keresztül bejelentkezett.", + "externalUsernameHint": "A $external.username paraméterrel töltse ki azt a nevet, amellyel SSO-n vagy LDAP-on keresztül bejelentkezett.", "jumpHostChain": "Jump Host Chain", "portKnocking": "Port Knocking", "addKnock": "Port hozzáadása", @@ -977,7 +977,7 @@ "noSearchResults": "Nincsenek a keresésnek megfelelő kapcsolatok", "rename": "Munkamenet átnevezése" }, - "terminal": { + "sshAuth": { "totpRequired": "Kétfaktoros hitelesítés szükséges", "totpCodeLabel": "Ellenőrző kód", "totpVerify": "Ellenőrzés", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "Nem sikerült ellenőrizni a visszaállítási kódot", "failedCompleteReset": "Nem sikerült befejezni a jelszó visszaállítását", "invalidTotpCode": "Érvénytelen TOTP-kód", - "failedOidcLogin": "Nem sikerült elindítani a külső bejelentkezést", - "silentSigninOidcUnavailable": "Csendes bejelentkezést kértek, de nincs beállítva külső bejelentkezés.", + "failedExternalLogin": "Nem sikerült elindítani a külső bejelentkezést", + "silentSigninExternalUnavailable": "Csendes bejelentkezést kértek, de nincs beállítva külső bejelentkezés.", "failedUserInfo": "Bejelentkezés után nem sikerült lekérni a felhasználói adatokat", - "oidcAuthFailed": "Külső bejelentkezés sikertelen", + "externalAuthFailed": "Külső bejelentkezés sikertelen", "invalidAuthUrl": "Érvénytelen engedélyezési URL érkezett a háttérrendszertől", "requiredField": "Ez a mező kötelező", "minLength": "A minimális hossz {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "Új felhasználók regisztrálhatnak felhasználónévvel és jelszóval", "allowPasswordLogin": "Jelszó bejelentkezés engedélyezése", "allowPasswordLoginDesc": "Felhasználónév/jelszó bejelentkezés", - "oidcAutoProvision": "Külső fiókok automatikus létrehozása", - "oidcAutoProvisionDesc": "Fiók létrehozása, amikor valaki először jelentkezik be külső bejelentkezéssel, például SSO-val vagy LDAP-pal, még akkor is, ha a regisztráció ki van kapcsolva.", + "externalAutoProvision": "Külső fiókok automatikus létrehozása", + "externalAutoProvisionDesc": "Fiók létrehozása, amikor valaki először jelentkezik be külső bejelentkezéssel, például SSO-val vagy LDAP-pal, még akkor is, ha a regisztráció ki van kapcsolva.", "secondFactorAfterExternalLogin": "Külső bejelentkezések (SSO, GitHub, Google, LDAP) után kérjen második tényezőt", "secondFactorAfterExternalLoginDesc": "Kikapcsolt állapotban a regisztrált második bejelentkezési tényezőt csak a jelszó és más helyi bejelentkezések után kéri a rendszer, ugyanúgy, mint korábban. Bekapcsolt állapotban minden bejelentkezési módszer után kéri, beleértve az SSO-t és az LDAP-t is.", "allowPasswordReset": "Jelszó-visszaállítás engedélyezése", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "Nincs lejárati idő", "revokedBadge": "VISSZAVONVA", "authTypeDual": "Kettős hitelesítés", - "authTypeOidc": "Külső", + "authTypeExternal": "Külső", "authTypeLocal": "Helyi", "adminStatusAdministrator": "Adminisztrátor", "adminStatusRegularUser": "Rendszeres felhasználó", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "Külső és jelszóval történő bejelentkezés engedélyezése", "linkAccountTargetUsername": "Helyi fiók felhasználóneve", "linkAccountTargetPlaceholder": "Add meg a helyi fiók felhasználónevét a csatoláshoz", - "linkAccountOidcUsername": "Külső fiók felhasználóneve", - "linkAccountOidcPlaceholder": "Adja meg a csak külső fiókhoz tartozó felhasználónevet az egyesítéshez", - "linkAccountOidcNotFound": "Nem található külső fiók ezzel a felhasználónévvel.", + "linkAccountExternalUsername": "Külső fiók felhasználóneve", + "linkAccountExternalPlaceholder": "Adja meg a csak külső fiókhoz tartozó felhasználónevet az egyesítéshez", + "linkAccountExternalNotFound": "Nem található külső fiók ezzel a felhasználónévvel.", "linkAccounts": "Fiókok összekapcsolása", "linkAccountSuccess": "Fiókok sikeresen összekapcsolva", "linkAccountFailed": "Nem sikerült összekapcsolni a fiókokat", @@ -1400,7 +1400,6 @@ "saving": "Megtakarítás...", "updateRegistrationFailed": "Nem sikerült frissíteni a regisztrációs beállításokat", "updatePasswordLoginFailed": "Nem sikerült frissíteni a jelszó bejelentkezési beállítását", - "updateOidcAutoProvisionFailed": "Nem sikerült frissíteni az automatikus létrehozási beállítást", "updateSecondFactorAfterExternalLoginFailed": "Nem sikerült frissíteni a második tényező beállítását", "updatePasswordResetFailed": "Nem sikerült frissíteni a jelszó-visszaállítási beállítást", "sessionTimeoutRange2": "A munkamenet időkorlátjának 1 és 720 óra között kell lennie.", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "BEZÁRT", "dataLockedNotice": "{{username}}adatai zárolva maradnak a következő bejelentkezésig. A hosztjaik, hitelesítő adataik és kódrészleteik addig nem tekinthetők meg és nem szerkeszthetők.", "resetPasswordTitle": "Jelszó visszaállítása", - "resetPasswordOidcOnly": "Ez a felhasználó egy külső szolgáltatón keresztül jelentkezik be, és nincs jelszava.", + "resetPasswordExternalOnly": "Ez a felhasználó egy külső szolgáltatón keresztül jelentkezik be, és nincs jelszava.", "resetPasswordPlaceholder": "Új jelszó", "resetPasswordBtn": "Visszaállítás", "resetPasswordWorking": "Visszaállítás...", @@ -1742,7 +1741,7 @@ "importFailed": "Importálás sikertelen: {{error}}", "roleUser": "Felhasználó", "authMethodDual": "Kettős hitelesítés", - "authMethodOidc": "Külső", + "authMethodExternal": "Külső", "done": "Kész", "apiKeyNameRequired": "Kulcsnév megadása kötelező", "apiKeyCreated": "„{{name}}” API-kulcs létrehozva", diff --git a/src/ui/locales/translated/id_ID.json b/src/ui/locales/translated/id_ID.json index 33d3a31c0..4769158b6 100644 --- a/src/ui/locales/translated/id_ID.json +++ b/src/ui/locales/translated/id_ID.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "Paksa Interaktif Keyboard", "overrideCredentialUsername": "Ganti Nama Pengguna Kredensial", "overrideCredentialUsernameDesc": "Gunakan nama pengguna yang telah ditentukan di atas, bukan nama pengguna kredensial.", - "oidcUsernameHint": "Gunakan $external.username untuk mengisi nama yang Anda gunakan saat masuk melalui SSO atau LDAP.", + "externalUsernameHint": "Gunakan $external.username untuk mengisi nama yang Anda gunakan saat masuk melalui SSO atau LDAP.", "jumpHostChain": "Rantai Host Lompatan", "portKnocking": "Ketukan Pelabuhan", "addKnock": "Tambahkan Port", @@ -977,7 +977,7 @@ "noSearchResults": "Tidak ada koneksi yang sesuai dengan pencarian Anda.", "rename": "Ganti nama sesi" }, - "terminal": { + "sshAuth": { "totpRequired": "Diperlukan Otentikasi Dua Faktor", "totpCodeLabel": "Kode Verifikasi", "totpVerify": "Memeriksa", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "Gagal memverifikasi kode reset", "failedCompleteReset": "Gagal menyelesaikan pengaturan ulang kata sandi.", "invalidTotpCode": "Kode TOTP tidak valid", - "failedOidcLogin": "Gagal memulai proses masuk eksternal.", - "silentSigninOidcUnavailable": "Login tanpa interaksi pengguna telah diminta, tetapi tidak ada login eksternal yang diatur.", + "failedExternalLogin": "Gagal memulai proses masuk eksternal.", + "silentSigninExternalUnavailable": "Login tanpa interaksi pengguna telah diminta, tetapi tidak ada login eksternal yang diatur.", "failedUserInfo": "Gagal mendapatkan informasi pengguna setelah login.", - "oidcAuthFailed": "Login eksternal gagal.", + "externalAuthFailed": "Login eksternal gagal.", "invalidAuthUrl": "URL otorisasi yang diterima dari backend tidak valid.", "requiredField": "Kolom ini wajib diisi.", "minLength": "Panjang minimumnya adalah {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "Izinkan pengguna baru mendaftar sendiri dengan nama pengguna dan kata sandi.", "allowPasswordLogin": "Izinkan Login dengan Kata Sandi", "allowPasswordLoginDesc": "Login dengan nama pengguna/kata sandi", - "oidcAutoProvision": "Buat akun eksternal secara otomatis", - "oidcAutoProvisionDesc": "Buat akun saat pertama kali seseorang masuk melalui login eksternal seperti SSO atau LDAP, bahkan saat pendaftaran dimatikan.", + "externalAutoProvision": "Buat akun eksternal secara otomatis", + "externalAutoProvisionDesc": "Buat akun saat pertama kali seseorang masuk melalui login eksternal seperti SSO atau LDAP, bahkan saat pendaftaran dimatikan.", "secondFactorAfterExternalLogin": "Mintalah faktor otentikasi kedua setelah login eksternal (SSO, GitHub, Google, LDAP)", "secondFactorAfterExternalLoginDesc": "Jika dinonaktifkan, faktor otentikasi kedua yang terdaftar hanya diminta setelah kata sandi dan login lokal lainnya, sama seperti sebelumnya. Jika diaktifkan, faktor otentikasi kedua diminta setelah setiap metode login, termasuk SSO dan LDAP.", "allowPasswordReset": "Izinkan Reset Kata Sandi", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "Tidak ada tanggal kedaluwarsa", "revokedBadge": "DICABUT", "authTypeDual": "Otorisasi Ganda", - "authTypeOidc": "Luar", + "authTypeExternal": "Luar", "authTypeLocal": "Lokal", "adminStatusAdministrator": "Administrator", "adminStatusRegularUser": "Pengguna Biasa", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "Izinkan login eksternal dan login menggunakan kata sandi.", "linkAccountTargetUsername": "Nama Pengguna Akun Lokal", "linkAccountTargetPlaceholder": "Masukkan nama pengguna akun lokal yang ingin dihubungkan.", - "linkAccountOidcUsername": "Nama Pengguna Akun Eksternal", - "linkAccountOidcPlaceholder": "Masukkan nama pengguna akun khusus eksternal yang ingin digabungkan.", - "linkAccountOidcNotFound": "Tidak ditemukan akun eksternal saja dengan nama pengguna tersebut.", + "linkAccountExternalUsername": "Nama Pengguna Akun Eksternal", + "linkAccountExternalPlaceholder": "Masukkan nama pengguna akun khusus eksternal yang ingin digabungkan.", + "linkAccountExternalNotFound": "Tidak ditemukan akun eksternal saja dengan nama pengguna tersebut.", "linkAccounts": "Tautkan Akun", "linkAccountSuccess": "Akun berhasil dihubungkan.", "linkAccountFailed": "Gagal menautkan akun", @@ -1400,7 +1400,6 @@ "saving": "Penghematan...", "updateRegistrationFailed": "Gagal memperbarui pengaturan pendaftaran", "updatePasswordLoginFailed": "Gagal memperbarui pengaturan login kata sandi.", - "updateOidcAutoProvisionFailed": "Gagal memperbarui pengaturan pembuatan otomatis.", "updateSecondFactorAfterExternalLoginFailed": "Gagal memperbarui pengaturan faktor kedua.", "updatePasswordResetFailed": "Gagal memperbarui pengaturan pengaturan ulang kata sandi.", "sessionTimeoutRange2": "Batas waktu sesi harus antara 1 dan 720 jam.", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "TERKUNCI", "dataLockedNotice": "Data {{username}}tetap terkunci hingga mereka masuk kembali. Host, kredensial, dan cuplikan mereka tidak dapat dilihat atau diedit hingga saat itu.", "resetPasswordTitle": "Atur Ulang Kata Sandi", - "resetPasswordOidcOnly": "Pengguna ini masuk melalui penyedia eksternal dan tidak memiliki kata sandi.", + "resetPasswordExternalOnly": "Pengguna ini masuk melalui penyedia eksternal dan tidak memiliki kata sandi.", "resetPasswordPlaceholder": "Kata sandi baru", "resetPasswordBtn": "Mengatur ulang", "resetPasswordWorking": "Mengatur ulang...", @@ -1742,7 +1741,7 @@ "importFailed": "Impor gagal: {{error}}", "roleUser": "Pengguna", "authMethodDual": "Otorisasi Ganda", - "authMethodOidc": "Luar", + "authMethodExternal": "Luar", "done": "Selesai", "apiKeyNameRequired": "Nama kunci wajib diisi.", "apiKeyCreated": "Kunci API \"{{name}}\" berhasil dibuat", diff --git a/src/ui/locales/translated/it_IT.json b/src/ui/locales/translated/it_IT.json index 78b5e4d88..c4a304baf 100644 --- a/src/ui/locales/translated/it_IT.json +++ b/src/ui/locales/translated/it_IT.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "Forza Keyboard-Interactive", "overrideCredentialUsername": "Sovrascrivi nome utente della credenziale", "overrideCredentialUsernameDesc": "Utilizza il nome utente specificato sopra invece di quello della credenziale", - "oidcUsernameHint": "Utilizza $external.username per inserire il nome con cui hai effettuato l'accesso tramite SSO o LDAP.", + "externalUsernameHint": "Utilizza $external.username per inserire il nome con cui hai effettuato l'accesso tramite SSO o LDAP.", "jumpHostChain": "Catena Jump Host", "portKnocking": "Port Knocking", "addKnock": "Aggiungi porta", @@ -977,7 +977,7 @@ "noSearchResults": "Nessuna connessione corrisponde alla ricerca", "rename": "Rinomina sessione" }, - "terminal": { + "sshAuth": { "totpRequired": "Autenticazione a due fattori richiesta", "totpCodeLabel": "Codice di verifica", "totpVerify": "Verifica", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "Verifica del codice di ripristino non riuscita", "failedCompleteReset": "Completamento del ripristino della password non riuscito", "invalidTotpCode": "Codice TOTP non valido", - "failedOidcLogin": "Impossibile avviare l'accesso esterno", - "silentSigninOidcUnavailable": "È stata richiesta l'attivazione dell'accesso silenzioso, ma non è stato configurato alcun accesso esterno.", + "failedExternalLogin": "Impossibile avviare l'accesso esterno", + "silentSigninExternalUnavailable": "È stata richiesta l'attivazione dell'accesso silenzioso, ma non è stato configurato alcun accesso esterno.", "failedUserInfo": "Impossibile ottenere le informazioni utente dopo l'accesso", - "oidcAuthFailed": "Accesso esterno non riuscito", + "externalAuthFailed": "Accesso esterno non riuscito", "invalidAuthUrl": "URL di autorizzazione non valido ricevuto dal backend", "requiredField": "Questo campo è obbligatorio", "minLength": "La lunghezza minima è {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "Consenti ai nuovi utenti di registrarsi autonomamente con nome utente e password", "allowPasswordLogin": "Consenti accesso con password", "allowPasswordLoginDesc": "Accesso con nome utente/password", - "oidcAutoProvision": "Creazione automatica di account esterni", - "oidcAutoProvisionDesc": "Crea un account la prima volta che qualcuno effettua l'accesso tramite un login esterno come SSO o LDAP, anche quando la registrazione è disattivata", + "externalAutoProvision": "Creazione automatica di account esterni", + "externalAutoProvisionDesc": "Crea un account la prima volta che qualcuno effettua l'accesso tramite un login esterno come SSO o LDAP, anche quando la registrazione è disattivata", "secondFactorAfterExternalLogin": "Richiedi un secondo fattore di autenticazione dopo l'accesso da fonti esterne (SSO, GitHub, Google, LDAP)", "secondFactorAfterExternalLoginDesc": "In modalità disattivata, l'autenticazione a due fattori viene richiesta solo dopo la password e altri accessi locali, come in precedenza. In modalità attivata, viene richiesta dopo ogni metodo di accesso, inclusi SSO e LDAP.", "allowPasswordReset": "Consenti reset password", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "Senza scadenza", "revokedBadge": "REVOCATO", "authTypeDual": "Autenticazione doppia", - "authTypeOidc": "Esterno", + "authTypeExternal": "Esterno", "authTypeLocal": "Locale", "adminStatusAdministrator": "Amministratore", "adminStatusRegularUser": "Utente regolare", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "Consenti l'accesso sia tramite credenziali esterne che tramite password.", "linkAccountTargetUsername": "Nome utente account locale", "linkAccountTargetPlaceholder": "Inserisci il nome utente dell'account locale da collegare", - "linkAccountOidcUsername": "Nome utente dell'account esterno", - "linkAccountOidcPlaceholder": "Inserisci il nome utente dell'account esterno da unire", - "linkAccountOidcNotFound": "Nessun account esterno trovato con quel nome utente", + "linkAccountExternalUsername": "Nome utente dell'account esterno", + "linkAccountExternalPlaceholder": "Inserisci il nome utente dell'account esterno da unire", + "linkAccountExternalNotFound": "Nessun account esterno trovato con quel nome utente", "linkAccounts": "Collega account", "linkAccountSuccess": "Account collegati con successo", "linkAccountFailed": "Collegamento account non riuscito", @@ -1400,7 +1400,6 @@ "saving": "Salvataggio in corso...", "updateRegistrationFailed": "Aggiornamento impostazione registrazione non riuscito", "updatePasswordLoginFailed": "Aggiornamento impostazione accesso con password non riuscito", - "updateOidcAutoProvisionFailed": "Impossibile aggiornare l'impostazione di creazione automatica", "updateSecondFactorAfterExternalLoginFailed": "Impossibile aggiornare l'impostazione del secondo fattore di autenticazione.", "updatePasswordResetFailed": "Aggiornamento impostazione reimpostazione password non riuscito", "sessionTimeoutRange2": "Il timeout della sessione deve essere compreso tra 1 e 720 ore", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "BLOCCATO", "dataLockedNotice": "I dati di {{username}} rimangono bloccati fino al prossimo accesso. I relativi host, credenziali e frammenti non possono essere visualizzati o modificati fino ad allora.", "resetPasswordTitle": "Reimposta password", - "resetPasswordOidcOnly": "Questo utente accede tramite un provider esterno e non ha una password.", + "resetPasswordExternalOnly": "Questo utente accede tramite un provider esterno e non ha una password.", "resetPasswordPlaceholder": "Nuova password", "resetPasswordBtn": "Reimposta", "resetPasswordWorking": "Reimpostazione in corso...", @@ -1742,7 +1741,7 @@ "importFailed": "Importazione non riuscita: {{error}}", "roleUser": "Utente", "authMethodDual": "Doppia autenticazione", - "authMethodOidc": "Esterno", + "authMethodExternal": "Esterno", "done": "Fatto", "apiKeyNameRequired": "Il nome della chiave è obbligatorio", "apiKeyCreated": "Chiave API \"{{name}}\" creata", diff --git a/src/ui/locales/translated/ja_JP.json b/src/ui/locales/translated/ja_JP.json index fd7fca1ff..d9a4217a4 100644 --- a/src/ui/locales/translated/ja_JP.json +++ b/src/ui/locales/translated/ja_JP.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "Keyboard-Interactive を強制", "overrideCredentialUsername": "認証情報のユーザー名を上書き", "overrideCredentialUsernameDesc": "認証情報のユーザー名の代わりに上記で指定したユーザー名を使用します", - "oidcUsernameHint": "$external.username を使用して、SSO または LDAP でサインインした名前を入力します。", + "externalUsernameHint": "$external.username を使用して、SSO または LDAP でサインインした名前を入力します。", "jumpHostChain": "ジャンプホストチェーン", "portKnocking": "ポートノッキング", "addKnock": "ポートを追加", @@ -977,7 +977,7 @@ "noSearchResults": "検索条件に一致する接続はありません", "rename": "セッション名を変更" }, - "terminal": { + "sshAuth": { "totpRequired": "二要素認証が必要です", "totpCodeLabel": "確認コード", "totpVerify": "確認", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "リセットコードの検証に失敗しました", "failedCompleteReset": "パスワードリセットの完了に失敗しました", "invalidTotpCode": "無効なTOTPコードです", - "failedOidcLogin": "外部サインインの開始に失敗しました", - "silentSigninOidcUnavailable": "サイレントサインインが要求されましたが、外部サインインは設定されていません。", + "failedExternalLogin": "外部サインインの開始に失敗しました", + "silentSigninExternalUnavailable": "サイレントサインインが要求されましたが、外部サインインは設定されていません。", "failedUserInfo": "ログイン後のユーザー情報の取得に失敗しました", - "oidcAuthFailed": "外部からのサインインに失敗しました", + "externalAuthFailed": "外部からのサインインに失敗しました", "invalidAuthUrl": "バックエンドから無効な認証URLを受信しました", "requiredField": "この項目は必須です", "minLength": "最小{{min}}文字です", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "新規ユーザーがユーザー名とパスワードで自己登録できます", "allowPasswordLogin": "パスワードログインを許可", "allowPasswordLoginDesc": "ユーザー名/パスワードログイン", - "oidcAutoProvision": "外部アカウントを自動作成", - "oidcAutoProvisionDesc": "SSOやLDAPなどの外部ログイン経由で初めてサインインした際に、登録が無効になっている場合でもアカウントを作成する", + "externalAutoProvision": "外部アカウントを自動作成", + "externalAutoProvisionDesc": "SSOやLDAPなどの外部ログイン経由で初めてサインインした際に、登録が無効になっている場合でもアカウントを作成する", "secondFactorAfterExternalLogin": "外部ログイン(SSO、GitHub、Google、LDAPなど)後に二段階認証を要求する", "secondFactorAfterExternalLoginDesc": "オフの場合、登録済みの二要素認証は、以前と同様にパスワードやその他のローカルログイン後にのみ要求されます。オンの場合、SSOやLDAPを含むすべてのログイン方法後に要求されます。", "allowPasswordReset": "パスワードリセットを許可", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "期限なし", "revokedBadge": "失効", "authTypeDual": "二要素認証", - "authTypeOidc": "外部", + "authTypeExternal": "外部", "authTypeLocal": "ローカル", "adminStatusAdministrator": "管理者", "adminStatusRegularUser": "一般ユーザー", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "外部アカウントとパスワードによるサインインの両方を許可する", "linkAccountTargetUsername": "ローカルアカウントのユーザー名", "linkAccountTargetPlaceholder": "リンク先のローカルアカウントのユーザー名を入力", - "linkAccountOidcUsername": "外部アカウントのユーザー名", - "linkAccountOidcPlaceholder": "外部専用アカウントのユーザー名を入力して統合します", - "linkAccountOidcNotFound": "そのユーザー名の外部専用アカウントは見つかりませんでした。", + "linkAccountExternalUsername": "外部アカウントのユーザー名", + "linkAccountExternalPlaceholder": "外部専用アカウントのユーザー名を入力して統合します", + "linkAccountExternalNotFound": "そのユーザー名の外部専用アカウントは見つかりませんでした。", "linkAccounts": "アカウントをリンク", "linkAccountSuccess": "アカウントのリンクに成功しました", "linkAccountFailed": "アカウントのリンクに失敗しました", @@ -1400,7 +1400,6 @@ "saving": "保存中...", "updateRegistrationFailed": "登録設定の更新に失敗しました", "updatePasswordLoginFailed": "パスワードログイン設定の更新に失敗しました", - "updateOidcAutoProvisionFailed": "自動作成設定の更新に失敗しました", "updateSecondFactorAfterExternalLoginFailed": "2段階認証設定の更新に失敗しました", "updatePasswordResetFailed": "パスワードリセット設定の更新に失敗しました", "sessionTimeoutRange2": "セッションタイムアウトは1時間から720時間の間である必要があります", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "ロック済み", "dataLockedNotice": "{{username}} のデータは次回ログインまでロックされたままです。それまで、ホスト、認証情報、スニペットの閲覧や編集はできません。", "resetPasswordTitle": "パスワードをリセット", - "resetPasswordOidcOnly": "このユーザーは外部プロバイダー経由でサインインしており、パスワードはありません。", + "resetPasswordExternalOnly": "このユーザーは外部プロバイダー経由でサインインしており、パスワードはありません。", "resetPasswordPlaceholder": "新しいパスワード", "resetPasswordBtn": "リセット", "resetPasswordWorking": "リセット中...", @@ -1742,7 +1741,7 @@ "importFailed": "インポートに失敗しました: {{error}}", "roleUser": "ユーザー", "authMethodDual": "二要素認証", - "authMethodOidc": "外部", + "authMethodExternal": "外部", "done": "完了", "apiKeyNameRequired": "キー名は必須です", "apiKeyCreated": "APIキー「{{name}}」が作成されました", diff --git a/src/ui/locales/translated/ko_KR.json b/src/ui/locales/translated/ko_KR.json index a9cc89a09..b1ea0acb2 100644 --- a/src/ui/locales/translated/ko_KR.json +++ b/src/ui/locales/translated/ko_KR.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "키보드 인터랙티브 강제", "overrideCredentialUsername": "자격 증명 사용자 이름 재정의", "overrideCredentialUsernameDesc": "자격 증명의 사용자 이름 대신 위에 지정된 사용자 이름을 사용합니다", - "oidcUsernameHint": "$external.username을 사용하여 SSO 또는 LDAP를 통해 로그인할 때 사용한 이름을 입력하세요.", + "externalUsernameHint": "$external.username을 사용하여 SSO 또는 LDAP를 통해 로그인할 때 사용한 이름을 입력하세요.", "jumpHostChain": "점프 호스트 체인", "portKnocking": "포트 노킹", "addKnock": "포트 추가", @@ -977,7 +977,7 @@ "noSearchResults": "검색과 일치하는 연결이 없습니다.", "rename": "세션 이름 바꾸기" }, - "terminal": { + "sshAuth": { "totpRequired": "2단계 인증 필요", "totpCodeLabel": "인증 코드", "totpVerify": "인증", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "재설정 코드 인증 실패", "failedCompleteReset": "비밀번호 재설정 완료 실패", "invalidTotpCode": "잘못된 TOTP 코드", - "failedOidcLogin": "외부 로그인 시작에 실패했습니다.", - "silentSigninOidcUnavailable": "자동 로그인 요청이 있었지만 외부 로그인은 설정되어 있지 않습니다.", + "failedExternalLogin": "외부 로그인 시작에 실패했습니다.", + "silentSigninExternalUnavailable": "자동 로그인 요청이 있었지만 외부 로그인은 설정되어 있지 않습니다.", "failedUserInfo": "로그인 후 사용자 정보를 가져오지 못했습니다", - "oidcAuthFailed": "외부 로그인 실패", + "externalAuthFailed": "외부 로그인 실패", "invalidAuthUrl": "백엔드에서 받은 잘못된 인증 URL", "requiredField": "필수 입력란입니다", "minLength": "최소 {{min}}자", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "새 사용자가 사용자 이름과 비밀번호로 자가 등록할 수 있도록 허용", "allowPasswordLogin": "비밀번호 로그인 허용", "allowPasswordLoginDesc": "사용자 이름/비밀번호 로그인", - "oidcAutoProvision": "외부 계정 자동 생성", - "oidcAutoProvisionDesc": "회원가입이 비활성화된 경우에도 SSO 또는 LDAP와 같은 외부 로그인을 통해 처음 로그인할 때 계정을 생성합니다.", + "externalAutoProvision": "외부 계정 자동 생성", + "externalAutoProvisionDesc": "회원가입이 비활성화된 경우에도 SSO 또는 LDAP와 같은 외부 로그인을 통해 처음 로그인할 때 계정을 생성합니다.", "secondFactorAfterExternalLogin": "외부 로그인(SSO, GitHub, Google, LDAP) 후에는 2단계 인증을 요청하세요.", "secondFactorAfterExternalLoginDesc": "비활성화된 경우, 등록된 2단계 인증은 이전과 마찬가지로 비밀번호 및 기타 로컬 로그인 후에만 요청됩니다. 활성화된 경우, SSO 및 LDAP를 포함한 모든 로그인 방식 후에 2단계 인증이 요청됩니다.", "allowPasswordReset": "비밀번호 재설정 허용", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "만료 없음", "revokedBadge": "취소됨", "authTypeDual": "이중 인증", - "authTypeOidc": "외부", + "authTypeExternal": "외부", "authTypeLocal": "로컬", "adminStatusAdministrator": "관리자", "adminStatusRegularUser": "일반 사용자", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "외부 로그인과 비밀번호 로그인을 모두 허용합니다.", "linkAccountTargetUsername": "로컬 계정 사용자 이름", "linkAccountTargetPlaceholder": "연결할 로컬 계정 사용자 이름 입력", - "linkAccountOidcUsername": "외부 계정 사용자 이름", - "linkAccountOidcPlaceholder": "병합할 외부 전용 계정 사용자 이름을 입력하세요.", - "linkAccountOidcNotFound": "해당 사용자 이름으로 된 외부 전용 계정을 찾을 수 없습니다.", + "linkAccountExternalUsername": "외부 계정 사용자 이름", + "linkAccountExternalPlaceholder": "병합할 외부 전용 계정 사용자 이름을 입력하세요.", + "linkAccountExternalNotFound": "해당 사용자 이름으로 된 외부 전용 계정을 찾을 수 없습니다.", "linkAccounts": "계정 연결", "linkAccountSuccess": "계정이 성공적으로 연결되었습니다.", "linkAccountFailed": "계정 연결에 실패했습니다.", @@ -1400,7 +1400,6 @@ "saving": "저장 중...", "updateRegistrationFailed": "등록 설정 업데이트에 실패했습니다.", "updatePasswordLoginFailed": "비밀번호 로그인 설정 업데이트에 실패했습니다.", - "updateOidcAutoProvisionFailed": "자동 생성 설정을 업데이트하는 데 실패했습니다.", "updateSecondFactorAfterExternalLoginFailed": "두 번째 인증 설정 업데이트에 실패했습니다.", "updatePasswordResetFailed": "비밀번호 재설정 설정 업데이트에 실패했습니다.", "sessionTimeoutRange2": "세션 제한 시간은 1시간 이상 720시간 이하여야 합니다.", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "잠김", "dataLockedNotice": "{{username}}님의 데이터는 다음 로그인까지 잠금 상태로 유지됩니다. 그때까지 호스트, 자격 증명 및 스니펫을 보거나 편집할 수 없습니다.", "resetPasswordTitle": "비밀번호 재설정", - "resetPasswordOidcOnly": "이 사용자는 외부 제공업체를 통해 로그인하며 비밀번호가 없습니다.", + "resetPasswordExternalOnly": "이 사용자는 외부 제공업체를 통해 로그인하며 비밀번호가 없습니다.", "resetPasswordPlaceholder": "새 비밀번호", "resetPasswordBtn": "재설정", "resetPasswordWorking": "재설정 중...", @@ -1742,7 +1741,7 @@ "importFailed": "가져오기 실패: {{error}}", "roleUser": "사용자", "authMethodDual": "이중 인증", - "authMethodOidc": "외부", + "authMethodExternal": "외부", "done": "완료", "apiKeyNameRequired": "API 키 이름이 필요합니다", "apiKeyCreated": "API 키 \"{{name}}\" 생성됨", diff --git a/src/ui/locales/translated/nl_NL.json b/src/ui/locales/translated/nl_NL.json index e4fa67318..dbfde762d 100644 --- a/src/ui/locales/translated/nl_NL.json +++ b/src/ui/locales/translated/nl_NL.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "Toetsenbordinteractief forceren", "overrideCredentialUsername": "Gebruikersnaam voor het overschrijven van inloggegevens", "overrideCredentialUsernameDesc": "Gebruik de hierboven opgegeven gebruikersnaam in plaats van de gebruikersnaam van de inloggegevens.", - "oidcUsernameHint": "Gebruik $external.username om de naam in te vullen waarmee u zich via SSO of LDAP hebt aangemeld.", + "externalUsernameHint": "Gebruik $external.username om de naam in te vullen waarmee u zich via SSO of LDAP hebt aangemeld.", "jumpHostChain": "Spring naar de hostketen", "portKnocking": "Havenkloppen", "addKnock": "Poort toevoegen", @@ -977,7 +977,7 @@ "noSearchResults": "Er zijn geen resultaten gevonden die overeenkomen met uw zoekopdracht.", "rename": "Sessie hernoemen" }, - "terminal": { + "sshAuth": { "totpRequired": "Tweefactorauthenticatie vereist", "totpCodeLabel": "Verificatiecode", "totpVerify": "Verifiëren", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "Het verifiëren van de resetcode is mislukt.", "failedCompleteReset": "Wachtwoordherstel is mislukt.", "invalidTotpCode": "Ongeldige TOTP-code", - "failedOidcLogin": "Externe aanmelding is mislukt.", - "silentSigninOidcUnavailable": "Er is een verzoek ingediend voor een stille aanmelding, maar er is geen externe aanmelding ingesteld.", + "failedExternalLogin": "Externe aanmelding is mislukt.", + "silentSigninExternalUnavailable": "Er is een verzoek ingediend voor een stille aanmelding, maar er is geen externe aanmelding ingesteld.", "failedUserInfo": "Het ophalen van gebruikersgegevens na het inloggen is mislukt.", - "oidcAuthFailed": "Externe aanmelding mislukt", + "externalAuthFailed": "Externe aanmelding mislukt", "invalidAuthUrl": "Ongeldige autorisatie-URL ontvangen van de backend.", "requiredField": "Dit veld is verplicht.", "minLength": "De minimale lengte is {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "Laat nieuwe gebruikers zich zelf registreren met een gebruikersnaam en wachtwoord.", "allowPasswordLogin": "Wachtwoordaanmelding toestaan", "allowPasswordLoginDesc": "Inloggen met gebruikersnaam/wachtwoord", - "oidcAutoProvision": "Externe accounts automatisch aanmaken", - "oidcAutoProvisionDesc": "Maak een account aan de eerste keer dat iemand inlogt via een externe login zoals SSO of LDAP, zelfs als registratie is uitgeschakeld.", + "externalAutoProvision": "Externe accounts automatisch aanmaken", + "externalAutoProvisionDesc": "Maak een account aan de eerste keer dat iemand inlogt via een externe login zoals SSO of LDAP, zelfs als registratie is uitgeschakeld.", "secondFactorAfterExternalLogin": "Vraag om een tweede authenticatiefactor na externe aanmeldingen (SSO, GitHub, Google, LDAP).", "secondFactorAfterExternalLoginDesc": "Uitgeschakeld: een geregistreerde tweede authenticatiefactor wordt alleen na het wachtwoord en andere lokale aanmeldingen gevraagd, net als voorheen. Aangeschakeld: deze wordt na elke aanmeldingsmethode gevraagd, inclusief SSO en LDAP.", "allowPasswordReset": "Wachtwoordherstel toestaan", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "Geen vervaldatum", "revokedBadge": "INGETROKKEN", "authTypeDual": "Dubbele authenticatie", - "authTypeOidc": "Extern", + "authTypeExternal": "Extern", "authTypeLocal": "Lokaal", "adminStatusAdministrator": "Beheerder", "adminStatusRegularUser": "Regelmatige gebruiker", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "Sta zowel externe aanmelding als aanmelding met wachtwoord toe.", "linkAccountTargetUsername": "Lokale accountgebruikersnaam", "linkAccountTargetPlaceholder": "Voer de gebruikersnaam van het lokale account in om te koppelen.", - "linkAccountOidcUsername": "Externe accountgebruikersnaam", - "linkAccountOidcPlaceholder": "Voer de gebruikersnaam van het externe account in om deze samen te voegen.", - "linkAccountOidcNotFound": "Er is geen extern account gevonden met die gebruikersnaam.", + "linkAccountExternalUsername": "Externe accountgebruikersnaam", + "linkAccountExternalPlaceholder": "Voer de gebruikersnaam van het externe account in om deze samen te voegen.", + "linkAccountExternalNotFound": "Er is geen extern account gevonden met die gebruikersnaam.", "linkAccounts": "Accounts koppelen", "linkAccountSuccess": "Accounts succesvol gekoppeld", "linkAccountFailed": "Het koppelen van accounts is mislukt.", @@ -1400,7 +1400,6 @@ "saving": "Besparing...", "updateRegistrationFailed": "Het bijwerken van de registratie-instellingen is mislukt.", "updatePasswordLoginFailed": "Het bijwerken van de wachtwoordinstellingen is mislukt.", - "updateOidcAutoProvisionFailed": "Het bijwerken van de instelling voor automatisch aanmaken is mislukt.", "updateSecondFactorAfterExternalLoginFailed": "Het bijwerken van de tweede authenticatie-instelling is mislukt.", "updatePasswordResetFailed": "Het bijwerken van de instellingen voor het opnieuw instellen van het wachtwoord is mislukt.", "sessionTimeoutRange2": "De sessietime-out moet tussen 1 en 720 uur liggen.", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "VERGRENDELD", "dataLockedNotice": "De gegevens van {{username}}blijven vergrendeld tot de volgende keer dat ze inloggen. Hun hosts, inloggegevens en codefragmenten kunnen tot die tijd niet worden bekeken of bewerkt.", "resetPasswordTitle": "Wachtwoord opnieuw instellen", - "resetPasswordOidcOnly": "Deze gebruiker meldt zich aan via een externe provider en heeft geen wachtwoord.", + "resetPasswordExternalOnly": "Deze gebruiker meldt zich aan via een externe provider en heeft geen wachtwoord.", "resetPasswordPlaceholder": "Nieuw wachtwoord", "resetPasswordBtn": "Reset", "resetPasswordWorking": "Opnieuw instellen...", @@ -1742,7 +1741,7 @@ "importFailed": "Import mislukt: {{error}}", "roleUser": "Gebruiker", "authMethodDual": "Dubbele authenticatie", - "authMethodOidc": "Extern", + "authMethodExternal": "Extern", "done": "Klaar", "apiKeyNameRequired": "Sleutelnaam is vereist", "apiKeyCreated": "API-sleutel \"{{name}}\" aangemaakt", diff --git a/src/ui/locales/translated/no_NO.json b/src/ui/locales/translated/no_NO.json index ee84c88e7..44f9e29da 100644 --- a/src/ui/locales/translated/no_NO.json +++ b/src/ui/locales/translated/no_NO.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "Tving tastaturinteraktivt", "overrideCredentialUsername": "Overstyr brukernavn for legitimasjon", "overrideCredentialUsernameDesc": "Bruk brukernavnet som er spesifisert ovenfor i stedet for brukernavnet til legitimasjonen", - "oidcUsernameHint": "Bruk $external.username for å fylle ut navnet du logget på med via SSO eller LDAP.", + "externalUsernameHint": "Bruk $external.username for å fylle ut navnet du logget på med via SSO eller LDAP.", "jumpHostChain": "Hoppvertkjede", "portKnocking": "Port Knocking", "addKnock": "Legg til port", @@ -977,7 +977,7 @@ "noSearchResults": "Ingen forbindelser samsvarer med søket ditt", "rename": "Gi nytt navn til økten" }, - "terminal": { + "sshAuth": { "totpRequired": "Tofaktorautentisering kreves", "totpCodeLabel": "Verifiseringskode", "totpVerify": "Verifisere", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "Kunne ikke bekrefte tilbakestillingskoden", "failedCompleteReset": "Kunne ikke fullføre tilbakestillingen av passordet", "invalidTotpCode": "Ugyldig TOTP-kode", - "failedOidcLogin": "Kunne ikke starte ekstern pålogging", - "silentSigninOidcUnavailable": "Stille pålogging ble forespurt, men ingen ekstern pålogging er konfigurert.", + "failedExternalLogin": "Kunne ikke starte ekstern pålogging", + "silentSigninExternalUnavailable": "Stille pålogging ble forespurt, men ingen ekstern pålogging er konfigurert.", "failedUserInfo": "Klarte ikke å hente brukerinformasjon etter innlogging", - "oidcAuthFailed": "Ekstern pålogging mislyktes", + "externalAuthFailed": "Ekstern pålogging mislyktes", "invalidAuthUrl": "Ugyldig autorisasjons-URL mottatt fra backend", "requiredField": "Dette feltet er obligatorisk", "minLength": "Minimumslengden er {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "La nye brukere registrere seg selv med brukernavn og passord", "allowPasswordLogin": "Tillat passordpålogging", "allowPasswordLoginDesc": "Brukernavn/passord pålogging", - "oidcAutoProvision": "Opprett eksterne kontoer automatisk", - "oidcAutoProvisionDesc": "Opprett en konto første gang noen logger på via en ekstern pålogging som SSO eller LDAP, selv når registreringen er av", + "externalAutoProvision": "Opprett eksterne kontoer automatisk", + "externalAutoProvisionDesc": "Opprett en konto første gang noen logger på via en ekstern pålogging som SSO eller LDAP, selv når registreringen er av", "secondFactorAfterExternalLogin": "Be om en annen faktor etter eksterne pålogginger (SSO, GitHub, Google, LDAP)", "secondFactorAfterExternalLoginDesc": "Av, en registrert sekundær faktor blir bare spurt etter passord og andre lokale pålogginger, samme som før. På, den blir spurt etter hver påloggingsmetode, inkludert SSO og LDAP.", "allowPasswordReset": "Tillat tilbakestilling av passord", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "Ingen utløpsdato", "revokedBadge": "OPPHEVET", "authTypeDual": "Dobbel autentisering", - "authTypeOidc": "Ekstern", + "authTypeExternal": "Ekstern", "authTypeLocal": "Lokalt", "adminStatusAdministrator": "Administrator", "adminStatusRegularUser": "Vanlig bruker", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "Tillat både ekstern pålogging og passordpålogging", "linkAccountTargetUsername": "Brukernavn for lokal konto", "linkAccountTargetPlaceholder": "Skriv inn brukernavnet for den lokale kontoen som skal kobles til", - "linkAccountOidcUsername": "Brukernavn for ekstern konto", - "linkAccountOidcPlaceholder": "Skriv inn brukernavnet for den eksterne kontoen som skal slås sammen", - "linkAccountOidcNotFound": "Ingen ekstern konto funnet med det brukernavnet", + "linkAccountExternalUsername": "Brukernavn for ekstern konto", + "linkAccountExternalPlaceholder": "Skriv inn brukernavnet for den eksterne kontoen som skal slås sammen", + "linkAccountExternalNotFound": "Ingen ekstern konto funnet med det brukernavnet", "linkAccounts": "Koble sammen kontoer", "linkAccountSuccess": "Kontoer er koblet sammen", "linkAccountFailed": "Kunne ikke koble kontoer", @@ -1400,7 +1400,6 @@ "saving": "Lagrer...", "updateRegistrationFailed": "Kunne ikke oppdatere registreringsinnstillingene", "updatePasswordLoginFailed": "Kunne ikke oppdatere passordinnstillingen for pålogging", - "updateOidcAutoProvisionFailed": "Kunne ikke oppdatere innstillingen for automatisk oppretting", "updateSecondFactorAfterExternalLoginFailed": "Kunne ikke oppdatere innstillingen for den andre faktoren", "updatePasswordResetFailed": "Kunne ikke oppdatere innstillingen for tilbakestilling av passord", "sessionTimeoutRange2": "Øktens tidsavbrudd må være mellom 1 og 720 timer", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "LÅST", "dataLockedNotice": "{{username}}sine data forblir låst til de logger seg inn igjen. Vertene, påloggingsinformasjonen og kodeutdragene deres kan ikke vises eller redigeres før da.", "resetPasswordTitle": "Tilbakestill passord", - "resetPasswordOidcOnly": "Denne brukeren logger seg på via en ekstern leverandør og har ikke noe passord.", + "resetPasswordExternalOnly": "Denne brukeren logger seg på via en ekstern leverandør og har ikke noe passord.", "resetPasswordPlaceholder": "Nytt passord", "resetPasswordBtn": "Tilbakestill", "resetPasswordWorking": "Tilbakestiller...", @@ -1742,7 +1741,7 @@ "importFailed": "Importen mislyktes: {{error}}", "roleUser": "Bruker", "authMethodDual": "Dobbel autentisering", - "authMethodOidc": "Ekstern", + "authMethodExternal": "Ekstern", "done": "Ferdig", "apiKeyNameRequired": "Nøkkelnavn er obligatorisk", "apiKeyCreated": "API-nøkkelen «{{name}}» ble opprettet", diff --git a/src/ui/locales/translated/pl_PL.json b/src/ui/locales/translated/pl_PL.json index 5d40da0ef..390576884 100644 --- a/src/ui/locales/translated/pl_PL.json +++ b/src/ui/locales/translated/pl_PL.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "Wymuś keyboard-interactive", "overrideCredentialUsername": "Zastąp nazwę użytkownika poświadczeń", "overrideCredentialUsernameDesc": "Użyj nazwy użytkownika określonej powyżej zamiast nazwy użytkownika podanej w poświadczeniu", - "oidcUsernameHint": "Użyj $external.username, aby wpisać nazwę, której użyłeś do zalogowania się za pomocą SSO lub LDAP.", + "externalUsernameHint": "Użyj $external.username, aby wpisać nazwę, której użyłeś do zalogowania się za pomocą SSO lub LDAP.", "jumpHostChain": "Łańcuch hostów pośrednich", "portKnocking": "Port Knocking", "addKnock": "Dodaj port", @@ -977,7 +977,7 @@ "noSearchResults": "Brak połączeń pasujących do wyszukiwania", "rename": "Zmień nazwę sesji" }, - "terminal": { + "sshAuth": { "totpRequired": "Wymagane uwierzytelnianie dwuskładnikowe", "totpCodeLabel": "Kod weryfikacyjny", "totpVerify": "Zweryfikuj", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "Nie udało się zweryfikować kodu resetowania", "failedCompleteReset": "Nie udało się ukończyć resetowania hasła", "invalidTotpCode": "Nieprawidłowy kod TOTP", - "failedOidcLogin": "Nie udało się rozpocząć logowania zewnętrznego", - "silentSigninOidcUnavailable": "Poproszono o ciche logowanie, ale nie skonfigurowano logowania zewnętrznego.", + "failedExternalLogin": "Nie udało się rozpocząć logowania zewnętrznego", + "silentSigninExternalUnavailable": "Poproszono o ciche logowanie, ale nie skonfigurowano logowania zewnętrznego.", "failedUserInfo": "Nie udało się pobrać informacji o użytkowniku po zalogowaniu", - "oidcAuthFailed": "Nie udało się zalogować z zewnątrz", + "externalAuthFailed": "Nie udało się zalogować z zewnątrz", "invalidAuthUrl": "Otrzymano nieprawidłowy adres URL autoryzacji z serwera", "requiredField": "To pole jest wymagane", "minLength": "Minimalna długość to {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "Pozwól nowym użytkownikom na samodzielną rejestrację przy użyciu nazwy użytkownika i hasła", "allowPasswordLogin": "Zezwalaj na logowanie hasłem", "allowPasswordLoginDesc": "Logowanie nazwą użytkownika i hasłem", - "oidcAutoProvision": "Automatyczne tworzenie kont zewnętrznych", - "oidcAutoProvisionDesc": "Utwórz konto za pierwszym razem, gdy ktoś zaloguje się za pomocą logowania zewnętrznego, takiego jak SSO lub LDAP, nawet jeśli rejestracja jest wyłączona", + "externalAutoProvision": "Automatyczne tworzenie kont zewnętrznych", + "externalAutoProvisionDesc": "Utwórz konto za pierwszym razem, gdy ktoś zaloguje się za pomocą logowania zewnętrznego, takiego jak SSO lub LDAP, nawet jeśli rejestracja jest wyłączona", "secondFactorAfterExternalLogin": "Poproś o drugi czynnik po logowaniu zewnętrznym (SSO, GitHub, Google, LDAP)", "secondFactorAfterExternalLoginDesc": "Wyłączona, drugi czynnik uwierzytelniania jest wymagany tylko po podaniu hasła i innych lokalnych logowaniach, tak jak poprzednio. Włączona, jest wymagany po każdej metodzie logowania, w tym SSO i LDAP.", "allowPasswordReset": "Zezwól na resetowanie hasła", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "Brak wygaśnięcia", "revokedBadge": "UNIEWAŻNIONY", "authTypeDual": "Podwójne uwierzytelnianie", - "authTypeOidc": "Zewnętrzny", + "authTypeExternal": "Zewnętrzny", "authTypeLocal": "Lokalne", "adminStatusAdministrator": "Administrator", "adminStatusRegularUser": "Zwykły użytkownik", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "Zezwalaj na logowanie zewnętrzne i za pomocą hasła", "linkAccountTargetUsername": "Nazwa użytkownika konta lokalnego", "linkAccountTargetPlaceholder": "Wprowadź nazwę użytkownika konta lokalnego, z którym chcesz je połączyć", - "linkAccountOidcUsername": "Nazwa użytkownika konta zewnętrznego", - "linkAccountOidcPlaceholder": "Wprowadź nazwę użytkownika konta przeznaczonego wyłącznie do użytku zewnętrznego, aby je scalić", - "linkAccountOidcNotFound": "Nie znaleziono konta przeznaczonego wyłącznie do użytku zewnętrznego o tej nazwie użytkownika", + "linkAccountExternalUsername": "Nazwa użytkownika konta zewnętrznego", + "linkAccountExternalPlaceholder": "Wprowadź nazwę użytkownika konta przeznaczonego wyłącznie do użytku zewnętrznego, aby je scalić", + "linkAccountExternalNotFound": "Nie znaleziono konta przeznaczonego wyłącznie do użytku zewnętrznego o tej nazwie użytkownika", "linkAccounts": "Połącz konta", "linkAccountSuccess": "Konta zostały pomyślnie połączone", "linkAccountFailed": "Nie udało się połączyć kont", @@ -1400,7 +1400,6 @@ "saving": "Zapisywanie...", "updateRegistrationFailed": "Nie udało się zaktualizować ustawień rejestracji", "updatePasswordLoginFailed": "Nie udało się zaktualizować ustawień logowania hasłem", - "updateOidcAutoProvisionFailed": "Nie udało się zaktualizować ustawienia automatycznego tworzenia", "updateSecondFactorAfterExternalLoginFailed": "Nie udało się zaktualizować ustawień drugiego czynnika uwierzytelniania", "updatePasswordResetFailed": "Nie udało się zaktualizować ustawienia resetowania hasła", "sessionTimeoutRange2": "Limit czasu sesji musi mieścić się w przedziale od 1 do 720 godzin", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "ZABLOKOWANE", "dataLockedNotice": "Dane użytkownika {{username}} pozostaną zablokowane do jego następnego zalogowania. Do tego czasu nie można przeglądać ani edytować jego hostów, poświadczeń i snippetów.", "resetPasswordTitle": "Zresetuj hasło", - "resetPasswordOidcOnly": "Ten użytkownik loguje się za pośrednictwem zewnętrznego dostawcy i nie ma hasła.", + "resetPasswordExternalOnly": "Ten użytkownik loguje się za pośrednictwem zewnętrznego dostawcy i nie ma hasła.", "resetPasswordPlaceholder": "Nowe hasło", "resetPasswordBtn": "Resetuj", "resetPasswordWorking": "Resetowanie...", @@ -1742,7 +1741,7 @@ "importFailed": "Import nie powiódł się: {{error}}", "roleUser": "Użytkownik", "authMethodDual": "Podwójne uwierzytelnianie", - "authMethodOidc": "Zewnętrzny", + "authMethodExternal": "Zewnętrzny", "done": "Gotowe", "apiKeyNameRequired": "Nazwa klucza jest wymagana", "apiKeyCreated": "Utworzono klucz API „{{name}}”", diff --git a/src/ui/locales/translated/pt_BR.json b/src/ui/locales/translated/pt_BR.json index e4c2cf438..43c50c99e 100644 --- a/src/ui/locales/translated/pt_BR.json +++ b/src/ui/locales/translated/pt_BR.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "Forçar interação por teclado", "overrideCredentialUsername": "Substituir nome de usuário da credencial", "overrideCredentialUsernameDesc": "Use o nome de usuário especificado acima em vez do nome de usuário da credencial.", - "oidcUsernameHint": "Use $external.username para preencher o nome com o qual você fez login por meio de SSO ou LDAP.", + "externalUsernameHint": "Use $external.username para preencher o nome com o qual você fez login por meio de SSO ou LDAP.", "jumpHostChain": "Cadeia de anfitriões de salto", "portKnocking": "Batendo ao porto", "addKnock": "Adicionar porta", @@ -977,7 +977,7 @@ "noSearchResults": "Nenhuma conexão corresponde à sua pesquisa.", "rename": "Renomear sessão" }, - "terminal": { + "sshAuth": { "totpRequired": "Autenticação de dois fatores obrigatória", "totpCodeLabel": "Código de verificação", "totpVerify": "Verificar", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "Falha ao verificar o código de reinicialização", "failedCompleteReset": "Falha ao concluir a redefinição de senha.", "invalidTotpCode": "Código TOTP inválido", - "failedOidcLogin": "Falha ao iniciar o login externo.", - "silentSigninOidcUnavailable": "Foi solicitado o login silencioso, mas nenhum login externo está configurado.", + "failedExternalLogin": "Falha ao iniciar o login externo.", + "silentSigninExternalUnavailable": "Foi solicitado o login silencioso, mas nenhum login externo está configurado.", "failedUserInfo": "Falha ao obter informações do usuário após o login", - "oidcAuthFailed": "Falha no login externo", + "externalAuthFailed": "Falha no login externo", "invalidAuthUrl": "URL de autorização inválida recebida do backend.", "requiredField": "Este campo é obrigatório", "minLength": "O comprimento mínimo é {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "Permita que novos usuários se cadastrem automaticamente com um nome de usuário e senha.", "allowPasswordLogin": "Permitir login com senha", "allowPasswordLoginDesc": "Login com nome de usuário/senha", - "oidcAutoProvision": "Criar contas externas automaticamente", - "oidcAutoProvisionDesc": "Criar uma conta na primeira vez que alguém fizer login por meio de um método externo, como SSO ou LDAP, mesmo quando o registro estiver desativado.", + "externalAutoProvision": "Criar contas externas automaticamente", + "externalAutoProvisionDesc": "Criar uma conta na primeira vez que alguém fizer login por meio de um método externo, como SSO ou LDAP, mesmo quando o registro estiver desativado.", "secondFactorAfterExternalLogin": "Solicitar um segundo fator de autenticação após logins externos (SSO, GitHub, Google, LDAP)", "secondFactorAfterExternalLoginDesc": "Desativado, o segundo fator de autenticação cadastrado só é solicitado após a senha e outros logins locais, como antes. Ativado, ele é solicitado após todos os métodos de login, incluindo SSO e LDAP.", "allowPasswordReset": "Permitir redefinição de senha", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "Sem data de validade", "revokedBadge": "REVOGADO", "authTypeDual": "Autenticação dupla", - "authTypeOidc": "Externo", + "authTypeExternal": "Externo", "authTypeLocal": "Local", "adminStatusAdministrator": "Administrador", "adminStatusRegularUser": "Usuário regular", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "Permitir login externo e com senha", "linkAccountTargetUsername": "Nome de usuário da conta local", "linkAccountTargetPlaceholder": "Insira o nome de usuário da conta local para vincular.", - "linkAccountOidcUsername": "Nome de usuário da conta externa", - "linkAccountOidcPlaceholder": "Insira o nome de usuário da conta externa para mesclar.", - "linkAccountOidcNotFound": "Nenhuma conta externa encontrada com esse nome de usuário.", + "linkAccountExternalUsername": "Nome de usuário da conta externa", + "linkAccountExternalPlaceholder": "Insira o nome de usuário da conta externa para mesclar.", + "linkAccountExternalNotFound": "Nenhuma conta externa encontrada com esse nome de usuário.", "linkAccounts": "Vincular contas", "linkAccountSuccess": "Contas vinculadas com sucesso", "linkAccountFailed": "Falha ao vincular as contas", @@ -1400,7 +1400,6 @@ "saving": "Salvando...", "updateRegistrationFailed": "Falha ao atualizar as configurações de registro", "updatePasswordLoginFailed": "Falha ao atualizar as configurações de login da senha.", - "updateOidcAutoProvisionFailed": "Falha ao atualizar a configuração de criação automática.", "updateSecondFactorAfterExternalLoginFailed": "Falha ao atualizar a configuração do segundo fator.", "updatePasswordResetFailed": "Falha ao atualizar as configurações de redefinição de senha", "sessionTimeoutRange2": "O tempo limite da sessão deve estar entre 1 e 720 horas.", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "TRANCADO", "dataLockedNotice": "Os dados de {{username}}permanecem bloqueados até o próximo login. Seus hosts, credenciais e trechos de código não podem ser visualizados ou editados até então.", "resetPasswordTitle": "Redefinir senha", - "resetPasswordOidcOnly": "Este usuário faz login por meio de um provedor externo e não possui senha.", + "resetPasswordExternalOnly": "Este usuário faz login por meio de um provedor externo e não possui senha.", "resetPasswordPlaceholder": "Nova Senha", "resetPasswordBtn": "Reiniciar", "resetPasswordWorking": "Reiniciando...", @@ -1742,7 +1741,7 @@ "importFailed": "A importação falhou: {{error}}", "roleUser": "Usuário", "authMethodDual": "Autenticação dupla", - "authMethodOidc": "Externo", + "authMethodExternal": "Externo", "done": "Feito", "apiKeyNameRequired": "É necessário nomear a chave.", "apiKeyCreated": "Chave de API \"{{name}}\" criada", diff --git a/src/ui/locales/translated/pt_PT.json b/src/ui/locales/translated/pt_PT.json index 6e78bb5c7..825e67166 100644 --- a/src/ui/locales/translated/pt_PT.json +++ b/src/ui/locales/translated/pt_PT.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "Forçar autenticação Keyboard-Interactive", "overrideCredentialUsername": "Substituir nome de utilizador da credencial", "overrideCredentialUsernameDesc": "Utilizar o nome de utilizador especificado acima em vez do nome de utilizador da credencial", - "oidcUsernameHint": "Utilize $external. username para preencher o nome com o qual iniciou sessão através de SSO ou LDAP.", + "externalUsernameHint": "Utilize $external. username para preencher o nome com o qual iniciou sessão através de SSO ou LDAP.", "jumpHostChain": "Cadeia de hosts de salto", "portKnocking": "Port Knocking", "addKnock": "Adicionar porta", @@ -977,7 +977,7 @@ "noSearchResults": "Nenhuma ligação corresponde à sua pesquisa", "rename": "Renomear sessão" }, - "terminal": { + "sshAuth": { "totpRequired": "Autenticação de dois fatores necessária", "totpCodeLabel": "Código de verificação", "totpVerify": "Verificar", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "Falha ao verificar o código de reposição", "failedCompleteReset": "Falha ao concluir a reposição da palavra-passe", "invalidTotpCode": "Código TOTP inválido", - "failedOidcLogin": "Falha ao iniciar o login externo.", - "silentSigninOidcUnavailable": "Foi solicitado o login silencioso, mas não está configurado nenhum login externo.", + "failedExternalLogin": "Falha ao iniciar o login externo.", + "silentSigninExternalUnavailable": "Foi solicitado o login silencioso, mas não está configurado nenhum login externo.", "failedUserInfo": "Falha ao obter informações do utilizador após o início de sessão", - "oidcAuthFailed": "Falha no login externo", + "externalAuthFailed": "Falha no login externo", "invalidAuthUrl": "URL de autorização inválida recebida do backend", "requiredField": "Este campo é obrigatório", "minLength": "O comprimento mínimo é {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "Permitir que novos utilizadores se auto-registem com nome de utilizador e palavra-passe", "allowPasswordLogin": "Permitir Início de Sessão com Palavra-passe", "allowPasswordLoginDesc": "Início de sessão com nome de utilizador/palavra-passe", - "oidcAutoProvision": "Criar contas externas automaticamente", - "oidcAutoProvisionDesc": "Criar uma conta na primeira vez que alguém inicia sessão através de um método externo, como SSO ou LDAP, mesmo quando o registo está desativado.", + "externalAutoProvision": "Criar contas externas automaticamente", + "externalAutoProvisionDesc": "Criar uma conta na primeira vez que alguém inicia sessão através de um método externo, como SSO ou LDAP, mesmo quando o registo está desativado.", "secondFactorAfterExternalLogin": "Solicitar um segundo fator de autenticação após os logins externos (SSO, GitHub, Google, LDAP)", "secondFactorAfterExternalLoginDesc": "Desativado, o segundo fator de autenticação registado só é solicitado após a password e outros logins locais, como anteriormente. Ativado, é solicitado após todos os métodos de login, incluindo SSO e LDAP.", "allowPasswordReset": "Permitir Reposição de Palavra-passe", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "Sem expiração", "revokedBadge": "REVOGADA", "authTypeDual": "Autenticação Dupla", - "authTypeOidc": "Externo", + "authTypeExternal": "Externo", "authTypeLocal": "Local", "adminStatusAdministrator": "Administrador", "adminStatusRegularUser": "Utilizador Regular", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "Permitir login externo e com password", "linkAccountTargetUsername": "Nome de utilizador da conta local", "linkAccountTargetPlaceholder": "Introduza o nome de utilizador da conta local a associar", - "linkAccountOidcUsername": "Nome de utilizador da conta externa", - "linkAccountOidcPlaceholder": "Introduza o nome de utilizador da conta externa para fundir.", - "linkAccountOidcNotFound": "Nenhuma conta externa encontrada com este nome de utilizador.", + "linkAccountExternalUsername": "Nome de utilizador da conta externa", + "linkAccountExternalPlaceholder": "Introduza o nome de utilizador da conta externa para fundir.", + "linkAccountExternalNotFound": "Nenhuma conta externa encontrada com este nome de utilizador.", "linkAccounts": "Associar contas", "linkAccountSuccess": "Contas associadas com sucesso", "linkAccountFailed": "Falha ao associar contas", @@ -1400,7 +1400,6 @@ "saving": "A guardar...", "updateRegistrationFailed": "Falha ao atualizar a definição de registo", "updatePasswordLoginFailed": "Falha ao atualizar a definição de início de sessão por palavra-passe", - "updateOidcAutoProvisionFailed": "Falha ao atualizar a configuração de criação automática.", "updateSecondFactorAfterExternalLoginFailed": "Falha ao atualizar a configuração do segundo fator.", "updatePasswordResetFailed": "Falha ao atualizar a definição de reposição de palavra-passe", "sessionTimeoutRange2": "O tempo limite da sessão deve estar entre 1 e 720 horas", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "BLOQUEADO", "dataLockedNotice": "Os dados de {{username}} permanecem bloqueados até ao próximo início de sessão. As suas máquinas, credenciais e fragmentos não podem ser visualizados nem editados até lá.", "resetPasswordTitle": "Redefinir palavra-passe", - "resetPasswordOidcOnly": "Este utilizador autentica-se através de um fornecedor externo e não tem palavra-passe.", + "resetPasswordExternalOnly": "Este utilizador autentica-se através de um fornecedor externo e não tem palavra-passe.", "resetPasswordPlaceholder": "Nova palavra-passe", "resetPasswordBtn": "Redefinir", "resetPasswordWorking": "A redefinir...", @@ -1742,7 +1741,7 @@ "importFailed": "A importação falhou: {{error}}", "roleUser": "Utilizador", "authMethodDual": "Autenticação Dupla", - "authMethodOidc": "Externo", + "authMethodExternal": "Externo", "done": "Concluído", "apiKeyNameRequired": "Nome da chave é obrigatório", "apiKeyCreated": "Chave de API \"{{name}}\" criada", diff --git a/src/ui/locales/translated/ro_RO.json b/src/ui/locales/translated/ro_RO.json index b1963164a..ac21516d0 100644 --- a/src/ui/locales/translated/ro_RO.json +++ b/src/ui/locales/translated/ro_RO.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "Forțați interacțiunea cu tastatura", "overrideCredentialUsername": "Suprascrierea acreditărilor de utilizator", "overrideCredentialUsernameDesc": "Folosește numele de utilizator specificat mai sus în locul numelui de utilizator al acreditării", - "oidcUsernameHint": "Folosește $external.username pentru a completa numele cu care te-ai conectat prin SSO sau LDAP.", + "externalUsernameHint": "Folosește $external.username pentru a completa numele cu care te-ai conectat prin SSO sau LDAP.", "jumpHostChain": "Lanțul gazdă Jump", "portKnocking": "Lovitura în port", "addKnock": "Adăugați port", @@ -977,7 +977,7 @@ "noSearchResults": "Nicio conexiune nu corespunde căutării dvs.", "rename": "Redenumiți sesiunea" }, - "terminal": { + "sshAuth": { "totpRequired": "Autentificare cu doi factori necesară", "totpCodeLabel": "Cod de verificare", "totpVerify": "Verifica", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "Nu s-a putut verifica codul de resetare", "failedCompleteReset": "Resetarea parolei nu a reușit", "invalidTotpCode": "Cod TOTP nevalid", - "failedOidcLogin": "Nu s-a putut porni conectarea externă", - "silentSigninOidcUnavailable": "A fost solicitată conectarea silențioasă, dar nu este configurată nicio conectare externă.", + "failedExternalLogin": "Nu s-a putut porni conectarea externă", + "silentSigninExternalUnavailable": "A fost solicitată conectarea silențioasă, dar nu este configurată nicio conectare externă.", "failedUserInfo": "Nu s-au putut obține informații despre utilizator după conectare", - "oidcAuthFailed": "Conectarea externă a eșuat", + "externalAuthFailed": "Conectarea externă a eșuat", "invalidAuthUrl": "Adresă URL de autorizare nevalidă primită de la backend", "requiredField": "Acest câmp este obligatoriu", "minLength": "Lungimea minimă este {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "Permiteți noilor utilizatori să se înregistreze automat cu un nume de utilizator și o parolă", "allowPasswordLogin": "Permiteți conectarea cu parolă", "allowPasswordLoginDesc": "Nume de utilizator/parolă de conectare", - "oidcAutoProvision": "Creați automat conturi externe", - "oidcAutoProvisionDesc": "Creați un cont prima dată când cineva se conectează printr-o conexiune externă, cum ar fi SSO sau LDAP, chiar și atunci când înregistrarea este dezactivată", + "externalAutoProvision": "Creați automat conturi externe", + "externalAutoProvisionDesc": "Creați un cont prima dată când cineva se conectează printr-o conexiune externă, cum ar fi SSO sau LDAP, chiar și atunci când înregistrarea este dezactivată", "secondFactorAfterExternalLogin": "Solicitați un al doilea factor după autentificările externe (SSO, GitHub, Google, LDAP)", "secondFactorAfterExternalLoginDesc": "Dezactivat, un al doilea factor înregistrat este solicitat doar după parolă și alte autentificări locale, la fel ca înainte. Activat, este solicitat după fiecare metodă de autentificare, inclusiv SSO și LDAP.", "allowPasswordReset": "Permite resetarea parolei", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "Fără expirare", "revokedBadge": "REVOCAT", "authTypeDual": "Autentificare duală", - "authTypeOidc": "Extern", + "authTypeExternal": "Extern", "authTypeLocal": "Local", "adminStatusAdministrator": "Administrator", "adminStatusRegularUser": "Utilizator obișnuit", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "Permiteți atât conectarea externă, cât și cea cu parolă", "linkAccountTargetUsername": "Nume de utilizator al contului local", "linkAccountTargetPlaceholder": "Introduceți numele de utilizator al contului local la care doriți să faceți legătura", - "linkAccountOidcUsername": "Nume de utilizator al contului extern", - "linkAccountOidcPlaceholder": "Introduceți numele de utilizator al contului extern pentru care doriți să îl îmbinați", - "linkAccountOidcNotFound": "Nu s-a găsit niciun cont extern cu acel nume de utilizator.", + "linkAccountExternalUsername": "Nume de utilizator al contului extern", + "linkAccountExternalPlaceholder": "Introduceți numele de utilizator al contului extern pentru care doriți să îl îmbinați", + "linkAccountExternalNotFound": "Nu s-a găsit niciun cont extern cu acel nume de utilizator.", "linkAccounts": "Conectare conturi", "linkAccountSuccess": "Conturi conectate cu succes", "linkAccountFailed": "Conectarea conturilor nu a reușit", @@ -1400,7 +1400,6 @@ "saving": "Economisire...", "updateRegistrationFailed": "Nu s-a putut actualiza setările de înregistrare", "updatePasswordLoginFailed": "Setările de conectare cu parola nu au putut fi actualizate.", - "updateOidcAutoProvisionFailed": "Nu s-a putut actualiza setarea de creare automată", "updateSecondFactorAfterExternalLoginFailed": "Nu s-a putut actualiza setarea celui de-al doilea factor", "updatePasswordResetFailed": "Setările de resetare a parolei nu au putut fi actualizate.", "sessionTimeoutRange2": "Timpul de expirare al sesiunii trebuie să fie între 1 și 720 de ore", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "BLOCAT", "dataLockedNotice": "Datele utilizatorului {{username}}rămân blocate până la următoarea conectare. Gazdele, acreditările și fragmentele de date nu pot fi vizualizate sau editate până atunci.", "resetPasswordTitle": "Resetare parolă", - "resetPasswordOidcOnly": "Acest utilizator se conectează printr-un furnizor extern și nu are parolă.", + "resetPasswordExternalOnly": "Acest utilizator se conectează printr-un furnizor extern și nu are parolă.", "resetPasswordPlaceholder": "Parolă Nouă", "resetPasswordBtn": "Resetare", "resetPasswordWorking": "Resetare...", @@ -1742,7 +1741,7 @@ "importFailed": "Importul a eșuat: {{error}}", "roleUser": "Utilizator", "authMethodDual": "Autentificare duală", - "authMethodOidc": "Extern", + "authMethodExternal": "Extern", "done": "Făcut", "apiKeyNameRequired": "Numele cheii este obligatoriu", "apiKeyCreated": "Cheia API „{{name}}” a fost creată", diff --git a/src/ui/locales/translated/ru_RU.json b/src/ui/locales/translated/ru_RU.json index 05ee48e22..43260ea4a 100644 --- a/src/ui/locales/translated/ru_RU.json +++ b/src/ui/locales/translated/ru_RU.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "Принудительно Keyboard-Interactive", "overrideCredentialUsername": "Переопределить имя пользователя учётных данных", "overrideCredentialUsernameDesc": "Использовать указанное выше имя пользователя вместо имени из учётных данных", - "oidcUsernameHint": "Используйте $external.username, чтобы указать имя, под которым вы вошли в систему через SSO или LDAP.", + "externalUsernameHint": "Используйте $external.username, чтобы указать имя, под которым вы вошли в систему через SSO или LDAP.", "jumpHostChain": "Цепочка Jump Host", "portKnocking": "Port Knocking", "addKnock": "Добавить порт", @@ -977,7 +977,7 @@ "noSearchResults": "Нет подключений, соответствующих поиску", "rename": "Переименовать сессию" }, - "terminal": { + "sshAuth": { "totpRequired": "Требуется двухфакторная аутентификация", "totpCodeLabel": "Код подтверждения", "totpVerify": "Подтвердить", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "Не удалось проверить код сброса", "failedCompleteReset": "Не удалось завершить сброс пароля", "invalidTotpCode": "Неверный TOTP-код", - "failedOidcLogin": "Не удалось запустить внешний вход в систему.", - "silentSigninOidcUnavailable": "Был запрошен бесшумный вход в систему, но внешний вход не настроен.", + "failedExternalLogin": "Не удалось запустить внешний вход в систему.", + "silentSigninExternalUnavailable": "Был запрошен бесшумный вход в систему, но внешний вход не настроен.", "failedUserInfo": "Не удалось получить информацию о пользователе после входа", - "oidcAuthFailed": "Внешняя авторизация не удалась", + "externalAuthFailed": "Внешняя авторизация не удалась", "invalidAuthUrl": "Получен некорректный URL авторизации от серверной части", "requiredField": "Обязательное поле", "minLength": "Минимальная длина: {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "Позволяет новым пользователям регистрироваться, указывая имя пользователя и пароль", "allowPasswordLogin": "Разрешить вход по паролю", "allowPasswordLoginDesc": "Вход по имени пользователя и паролю", - "oidcAutoProvision": "Автоматическое создание внешних учетных записей", - "oidcAutoProvisionDesc": "При первом входе пользователя через внешний источник авторизации, такой как SSO или LDAP, учетная запись создается, даже если регистрация отключена.", + "externalAutoProvision": "Автоматическое создание внешних учетных записей", + "externalAutoProvisionDesc": "При первом входе пользователя через внешний источник авторизации, такой как SSO или LDAP, учетная запись создается, даже если регистрация отключена.", "secondFactorAfterExternalLogin": "После авторизации через внешние сервисы (SSO, GitHub, Google, LDAP) может потребоваться дополнительная авторизация.", "secondFactorAfterExternalLoginDesc": "Если отключено, зарегистрированный второй фактор аутентификации запрашивается только после ввода пароля и других локальных учетных записей, как и раньше. Если включено, он запрашивается после каждого метода входа в систему, включая SSO и LDAP.", "allowPasswordReset": "Разрешить сброс пароля", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "Без срока действия", "revokedBadge": "ОТОЗВАН", "authTypeDual": "Двухфакторная аутентификация", - "authTypeOidc": "Внешний", + "authTypeExternal": "Внешний", "authTypeLocal": "Локальная", "adminStatusAdministrator": "Администратор", "adminStatusRegularUser": "Обычный пользователь", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "Разрешите вход как извне, так и по паролю.", "linkAccountTargetUsername": "Имя пользователя локального аккаунта", "linkAccountTargetPlaceholder": "Введите имя пользователя локального аккаунта для связывания", - "linkAccountOidcUsername": "Имя пользователя внешней учетной записи", - "linkAccountOidcPlaceholder": "Введите имя пользователя внешней учетной записи для объединения.", - "linkAccountOidcNotFound": "Внешняя учетная запись с таким именем пользователя не найдена.", + "linkAccountExternalUsername": "Имя пользователя внешней учетной записи", + "linkAccountExternalPlaceholder": "Введите имя пользователя внешней учетной записи для объединения.", + "linkAccountExternalNotFound": "Внешняя учетная запись с таким именем пользователя не найдена.", "linkAccounts": "Связать аккаунты", "linkAccountSuccess": "Аккаунты успешно связаны", "linkAccountFailed": "Не удалось связать аккаунты", @@ -1400,7 +1400,6 @@ "saving": "Сохранение...", "updateRegistrationFailed": "Не удалось обновить настройки регистрации", "updatePasswordLoginFailed": "Не удалось обновить настройки входа по паролю", - "updateOidcAutoProvisionFailed": "Не удалось обновить настройку автоматического создания.", "updateSecondFactorAfterExternalLoginFailed": "Не удалось обновить настройки второго фактора.", "updatePasswordResetFailed": "Не удалось обновить настройки сброса пароля", "sessionTimeoutRange2": "Тайм-аут сеанса должен быть от 1 до 720 часов", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "ЗАБЛОКИРОВАНО", "dataLockedNotice": "Данные {{username}} остаются заблокированными до следующего входа. Хосты, учётные данные и сниппеты нельзя просмотреть или отредактировать до тех пор.", "resetPasswordTitle": "Сброс пароля", - "resetPasswordOidcOnly": "Этот пользователь входит через внешнего провайдера и не имеет пароля.", + "resetPasswordExternalOnly": "Этот пользователь входит через внешнего провайдера и не имеет пароля.", "resetPasswordPlaceholder": "Новый пароль", "resetPasswordBtn": "Сбросить", "resetPasswordWorking": "Сброс...", @@ -1742,7 +1741,7 @@ "importFailed": "Импорт не удался: {{error}}", "roleUser": "Пользователь", "authMethodDual": "Двойная аутентификация", - "authMethodOidc": "Внешний", + "authMethodExternal": "Внешний", "done": "Готово", "apiKeyNameRequired": "Требуется имя ключа", "apiKeyCreated": "API-ключ «{{name}}» создан", diff --git a/src/ui/locales/translated/sr_SP.json b/src/ui/locales/translated/sr_SP.json index 2f7046f8c..fda5f8e6c 100644 --- a/src/ui/locales/translated/sr_SP.json +++ b/src/ui/locales/translated/sr_SP.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "Присилна интерактивност тастатуре", "overrideCredentialUsername": "Замени корисничко име акредитива", "overrideCredentialUsernameDesc": "Користите корисничко име наведено горе уместо корисничког имена акредитива", - "oidcUsernameHint": "Користите $external.корисничко име да бисте попунили име са којим сте се пријавили путем SSO или LDAP.", + "externalUsernameHint": "Користите $external.корисничко име да бисте попунили име са којим сте се пријавили путем SSO или LDAP.", "jumpHostChain": "Скок ланца хоста", "portKnocking": "Лук Нокинг", "addKnock": "Додај порт", @@ -977,7 +977,7 @@ "noSearchResults": "Ниједна веза не одговара вашој претрази", "rename": "Преименуј сесију" }, - "terminal": { + "sshAuth": { "totpRequired": "Потребна је двофакторска аутентификација", "totpCodeLabel": "Верификациони код", "totpVerify": "Верификуј", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "Није успело потврђивање кода за ресетовање", "failedCompleteReset": "Ресетовање лозинке није успело", "invalidTotpCode": "Неважећи TOTP код", - "failedOidcLogin": "Покретање екстерног пријављивања није успело", - "silentSigninOidcUnavailable": "Затражено је тихо пријављивање, али није подешено спољно пријављивање.", + "failedExternalLogin": "Покретање екстерног пријављивања није успело", + "silentSigninExternalUnavailable": "Затражено је тихо пријављивање, али није подешено спољно пријављивање.", "failedUserInfo": "Није успело добијање корисничких података након пријаве", - "oidcAuthFailed": "Спољно пријављивање није успело", + "externalAuthFailed": "Спољно пријављивање није успело", "invalidAuthUrl": "Неважећи URL за ауторизацију примљен од бекенда", "requiredField": "Ово поље је обавезно", "minLength": "Минимална дужина је {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "Дозволите новим корисницима да се сами региструју помоћу корисничког имена и лозинке", "allowPasswordLogin": "Дозволи пријаву лозинком", "allowPasswordLoginDesc": "Пријава корисничким именом/лозинком", - "oidcAutoProvision": "Аутоматски креирај екстерне налоге", - "oidcAutoProvisionDesc": "Креирајте налог први пут када се неко пријави путем екстерног пријављивања као што је SSO или LDAP, чак и када је регистрација искључена", + "externalAutoProvision": "Аутоматски креирај екстерне налоге", + "externalAutoProvisionDesc": "Креирајте налог први пут када се неко пријави путем екстерног пријављивања као што је SSO или LDAP, чак и када је регистрација искључена", "secondFactorAfterExternalLogin": "Захтевајте други фактор након екстерних пријављивања (SSO, GitHub, Google, LDAP)", "secondFactorAfterExternalLoginDesc": "Искључено, регистровани други фактор се тражи само након лозинке и других локалних пријављивања, исто као и пре. Укључено, тражи се након сваке методе пријављивања, укључујући SSO и LDAP.", "allowPasswordReset": "Дозволи ресетовање лозинке", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "Без истека", "revokedBadge": "ПОНИШТЕНО", "authTypeDual": "Двострука аутентификација", - "authTypeOidc": "Спољни", + "authTypeExternal": "Спољни", "authTypeLocal": "Локално", "adminStatusAdministrator": "Администратор", "adminStatusRegularUser": "Редован корисник", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "Дозволи и спољашње пријављивање и пријављивање лозинком", "linkAccountTargetUsername": "Корисничко име локалног налога", "linkAccountTargetPlaceholder": "Унесите корисничко име локалног налога за повезивање", - "linkAccountOidcUsername": "Корисничко име екстерног налога", - "linkAccountOidcPlaceholder": "Унесите корисничко име само за екстерни налог за спајање", - "linkAccountOidcNotFound": "Није пронађен ниједан спољни налог са тим корисничким именом", + "linkAccountExternalUsername": "Корисничко име екстерног налога", + "linkAccountExternalPlaceholder": "Унесите корисничко име само за екстерни налог за спајање", + "linkAccountExternalNotFound": "Није пронађен ниједан спољни налог са тим корисничким именом", "linkAccounts": "Повежи налоге", "linkAccountSuccess": "Налози су успешно повезани", "linkAccountFailed": "Повезивање налога није успело", @@ -1400,7 +1400,6 @@ "saving": "Чување...", "updateRegistrationFailed": "Ажурирање подешавања регистрације није успело", "updatePasswordLoginFailed": "Ажурирање подешавања за пријаву лозинком није успело", - "updateOidcAutoProvisionFailed": "Ажурирање подешавања аутоматског креирања није успело", "updateSecondFactorAfterExternalLoginFailed": "Ажурирање подешавања другог фактора није успело", "updatePasswordResetFailed": "Ажурирање подешавања за ресетовање лозинке није успело", "sessionTimeoutRange2": "Временско ограничење сесије мора бити између 1 и 720 сати", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "ЗАКЉУЧАНО", "dataLockedNotice": "Подаци корисника {{username}}остају закључани до њиховог следећег пријављивања. Њихови хостови, акредитиви и исечци не могу бити прегледани или уређивани до тада.", "resetPasswordTitle": "Ресетуј лозинку", - "resetPasswordOidcOnly": "Овај корисник се пријављује преко екстерног провајдера и нема лозинку.", + "resetPasswordExternalOnly": "Овај корисник се пријављује преко екстерног провајдера и нема лозинку.", "resetPasswordPlaceholder": "Нова лозинка", "resetPasswordBtn": "Ресетуј", "resetPasswordWorking": "Ресетовање...", @@ -1742,7 +1741,7 @@ "importFailed": "Увоз није успео: {{error}}", "roleUser": "Корисник", "authMethodDual": "Двострука аутентификација", - "authMethodOidc": "Спољни", + "authMethodExternal": "Спољни", "done": "Готово", "apiKeyNameRequired": "Назив кључа је обавезан", "apiKeyCreated": "API кључ „{{name}}“ је креиран", diff --git a/src/ui/locales/translated/sv_SE.json b/src/ui/locales/translated/sv_SE.json index 4393bb5c8..29c6caf31 100644 --- a/src/ui/locales/translated/sv_SE.json +++ b/src/ui/locales/translated/sv_SE.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "Tvinga tangentbordsinteraktivt", "overrideCredentialUsername": "Åsidosätt användarnamn för inloggningsuppgifter", "overrideCredentialUsernameDesc": "Använd användarnamnet som anges ovan istället för användarnamnet för inloggningsuppgifterna", - "oidcUsernameHint": "Använd $external.username för att fylla i namnet du loggade in med via SSO eller LDAP.", + "externalUsernameHint": "Använd $external.username för att fylla i namnet du loggade in med via SSO eller LDAP.", "jumpHostChain": "Hoppa värdkedja", "portKnocking": "Port Knocking", "addKnock": "Lägg till port", @@ -977,7 +977,7 @@ "noSearchResults": "Inga kontakter matchar din sökning", "rename": "Byt namn på sessionen" }, - "terminal": { + "sshAuth": { "totpRequired": "Tvåfaktorsautentisering krävs", "totpCodeLabel": "Verifieringskod", "totpVerify": "Kontrollera", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "Misslyckades med att verifiera återställningskoden", "failedCompleteReset": "Misslyckades med att slutföra återställning av lösenord", "invalidTotpCode": "Ogiltig TOTP-kod", - "failedOidcLogin": "Misslyckades med att starta extern inloggning", - "silentSigninOidcUnavailable": "Tyst inloggning begärdes, men ingen extern inloggning är konfigurerad.", + "failedExternalLogin": "Misslyckades med att starta extern inloggning", + "silentSigninExternalUnavailable": "Tyst inloggning begärdes, men ingen extern inloggning är konfigurerad.", "failedUserInfo": "Misslyckades med att hämta användarinformation efter inloggning", - "oidcAuthFailed": "Extern inloggning misslyckades", + "externalAuthFailed": "Extern inloggning misslyckades", "invalidAuthUrl": "Ogiltig auktoriserings-URL mottagen från backend", "requiredField": "Detta fält är obligatoriskt", "minLength": "Minimilängden är {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "Låt nya användare registrera sig själva med användarnamn och lösenord", "allowPasswordLogin": "Tillåt lösenordsinloggning", "allowPasswordLoginDesc": "Användarnamn/lösenord inloggning", - "oidcAutoProvision": "Skapa externa konton automatiskt", - "oidcAutoProvisionDesc": "Skapa ett konto första gången någon loggar in via en extern inloggning som SSO eller LDAP, även när registreringen är avstängd", + "externalAutoProvision": "Skapa externa konton automatiskt", + "externalAutoProvisionDesc": "Skapa ett konto första gången någon loggar in via en extern inloggning som SSO eller LDAP, även när registreringen är avstängd", "secondFactorAfterExternalLogin": "Be om en andra faktor efter externa inloggningar (SSO, GitHub, Google, LDAP)", "secondFactorAfterExternalLoginDesc": "Av, en registrerad andra faktor efterfrågas endast efter lösenord och andra lokala inloggningar, samma som tidigare. På, den efterfrågas efter varje inloggningsmetod, inklusive SSO och LDAP.", "allowPasswordReset": "Tillåt lösenordsåterställning", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "Ingen utgångsdatum", "revokedBadge": "ÅTERKALLAD", "authTypeDual": "Dubbel autentisering", - "authTypeOidc": "Extern", + "authTypeExternal": "Extern", "authTypeLocal": "Lokal", "adminStatusAdministrator": "Administratör", "adminStatusRegularUser": "Vanlig användare", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "Tillåt både extern inloggning och lösenordsinloggning", "linkAccountTargetUsername": "Användarnamn för lokalt konto", "linkAccountTargetPlaceholder": "Ange användarnamnet för det lokala kontot som ska länkas till", - "linkAccountOidcUsername": "Användarnamn för externt konto", - "linkAccountOidcPlaceholder": "Ange användarnamnet för det endast externa kontot som ska sammanfogas", - "linkAccountOidcNotFound": "Inget externt konto hittades med det användarnamnet", + "linkAccountExternalUsername": "Användarnamn för externt konto", + "linkAccountExternalPlaceholder": "Ange användarnamnet för det endast externa kontot som ska sammanfogas", + "linkAccountExternalNotFound": "Inget externt konto hittades med det användarnamnet", "linkAccounts": "Länka konton", "linkAccountSuccess": "Konton har länkats", "linkAccountFailed": "Misslyckades med att länka konton", @@ -1400,7 +1400,6 @@ "saving": "Sparande...", "updateRegistrationFailed": "Misslyckades med att uppdatera registreringsinställningen", "updatePasswordLoginFailed": "Misslyckades med att uppdatera lösenordsinställningen för inloggning", - "updateOidcAutoProvisionFailed": "Misslyckades med att uppdatera inställningen för automatiskt skapande", "updateSecondFactorAfterExternalLoginFailed": "Misslyckades med att uppdatera inställningen för den andra faktorn", "updatePasswordResetFailed": "Misslyckades med att uppdatera inställningen för lösenordsåterställning", "sessionTimeoutRange2": "Sessionens tidsgräns måste vara mellan 1 och 720 timmar", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "LÅST", "dataLockedNotice": "{{username}}s data förblir låsta tills de loggar in nästa gång. Deras värdar, inloggningsuppgifter och utdrag kan inte visas eller redigeras förrän då.", "resetPasswordTitle": "Återställ lösenord", - "resetPasswordOidcOnly": "Den här användaren loggar in via en extern leverantör och har inget lösenord.", + "resetPasswordExternalOnly": "Den här användaren loggar in via en extern leverantör och har inget lösenord.", "resetPasswordPlaceholder": "Nytt lösenord", "resetPasswordBtn": "Återställa", "resetPasswordWorking": "Återställer...", @@ -1742,7 +1741,7 @@ "importFailed": "Importen misslyckades: {{error}}", "roleUser": "Användare", "authMethodDual": "Dubbel autentisering", - "authMethodOidc": "Extern", + "authMethodExternal": "Extern", "done": "Gjort", "apiKeyNameRequired": "Nyckelnamn krävs", "apiKeyCreated": "API-nyckeln \"{{name}}\" skapad", diff --git a/src/ui/locales/translated/th_TH.json b/src/ui/locales/translated/th_TH.json index 6012303b0..f7975ee86 100644 --- a/src/ui/locales/translated/th_TH.json +++ b/src/ui/locales/translated/th_TH.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "แป้นพิมพ์แบบอินเทอร์แอคทีฟของ Force", "overrideCredentialUsername": "แทนที่ชื่อผู้ใช้ข้อมูลประจำตัว", "overrideCredentialUsernameDesc": "ใช้ชื่อผู้ใช้ที่ระบุไว้ข้างต้นแทนชื่อผู้ใช้ของข้อมูลประจำตัว", - "oidcUsernameHint": "ใช้ $external.username เพื่อกรอกชื่อที่คุณใช้เข้าสู่ระบบผ่าน SSO หรือ LDAP", + "externalUsernameHint": "ใช้ $external.username เพื่อกรอกชื่อที่คุณใช้เข้าสู่ระบบผ่าน SSO หรือ LDAP", "jumpHostChain": "ห่วงโซ่ Jump Host", "portKnocking": "การเคาะพอร์ต", "addKnock": "เพิ่มพอร์ต", @@ -977,7 +977,7 @@ "noSearchResults": "ไม่พบผลลัพธ์ที่ตรงกับการค้นหาของคุณ", "rename": "เปลี่ยนชื่อเซสชัน" }, - "terminal": { + "sshAuth": { "totpRequired": "ต้องใช้การยืนยันตัวตนสองขั้นตอน", "totpCodeLabel": "รหัสยืนยัน", "totpVerify": "ตรวจสอบ", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "ไม่สามารถตรวจสอบรหัสรีเซ็ตได้", "failedCompleteReset": "การรีเซ็ตรหัสผ่านล้มเหลว", "invalidTotpCode": "รหัส TOTP ไม่ถูกต้อง", - "failedOidcLogin": "ไม่สามารถเริ่มต้นการเข้าสู่ระบบจากภายนอกได้", - "silentSigninOidcUnavailable": "มีการร้องขอการเข้าสู่ระบบแบบเงียบ แต่ไม่ได้ตั้งค่าการเข้าสู่ระบบภายนอกไว้", + "failedExternalLogin": "ไม่สามารถเริ่มต้นการเข้าสู่ระบบจากภายนอกได้", + "silentSigninExternalUnavailable": "มีการร้องขอการเข้าสู่ระบบแบบเงียบ แต่ไม่ได้ตั้งค่าการเข้าสู่ระบบภายนอกไว้", "failedUserInfo": "ไม่สามารถดึงข้อมูลผู้ใช้ได้หลังจากเข้าสู่ระบบ", - "oidcAuthFailed": "การเข้าสู่ระบบจากภายนอกล้มเหลว", + "externalAuthFailed": "การเข้าสู่ระบบจากภายนอกล้มเหลว", "invalidAuthUrl": "ได้รับ URL การอนุญาตที่ไม่ถูกต้องจากแบ็กเอนด์", "requiredField": "ช่องนี้จำเป็นต้องกรอก", "minLength": "ความยาวขั้นต่ำคือ {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "อนุญาตให้ผู้ใช้ใหม่ลงทะเบียนด้วยตนเองโดยใช้ชื่อผู้ใช้และรหัสผ่าน", "allowPasswordLogin": "อนุญาตให้เข้าสู่ระบบด้วยรหัสผ่าน", "allowPasswordLoginDesc": "เข้าสู่ระบบด้วยชื่อผู้ใช้/รหัสผ่าน", - "oidcAutoProvision": "สร้างบัญชีภายนอกโดยอัตโนมัติ", - "oidcAutoProvisionDesc": "สร้างบัญชีผู้ใช้ในครั้งแรกที่มีคนเข้าสู่ระบบผ่านการเข้าสู่ระบบภายนอก เช่น SSO หรือ LDAP แม้ว่าการลงทะเบียนจะปิดอยู่ก็ตาม", + "externalAutoProvision": "สร้างบัญชีภายนอกโดยอัตโนมัติ", + "externalAutoProvisionDesc": "สร้างบัญชีผู้ใช้ในครั้งแรกที่มีคนเข้าสู่ระบบผ่านการเข้าสู่ระบบภายนอก เช่น SSO หรือ LDAP แม้ว่าการลงทะเบียนจะปิดอยู่ก็ตาม", "secondFactorAfterExternalLogin": "ขอให้ยืนยันตัวตนสองขั้นตอนหลังจากเข้าสู่ระบบจากภายนอก (SSO, GitHub, Google, LDAP)", "secondFactorAfterExternalLoginDesc": "หากปิดใช้งาน ระบบจะขอการยืนยันตัวตนสองขั้นตอนที่ลงทะเบียนไว้เฉพาะหลังจากป้อนรหัสผ่านและการเข้าสู่ระบบภายในเครื่องอื่นๆ เท่านั้น เช่นเดียวกับก่อนหน้านี้ หากเปิดใช้งาน ระบบจะขอการยืนยันตัวตนสองขั้นตอนหลังจากวิธีการเข้าสู่ระบบทุกวิธี รวมถึง SSO และ LDAP", "allowPasswordReset": "อนุญาตให้รีเซ็ตรหัสผ่าน", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "ไม่มีวันหมดอายุ", "revokedBadge": "เพิกถอน", "authTypeDual": "การตรวจสอบสิทธิ์แบบคู่", - "authTypeOidc": "ภายนอก", + "authTypeExternal": "ภายนอก", "authTypeLocal": "ท้องถิ่น", "adminStatusAdministrator": "ผู้ดูแลระบบ", "adminStatusRegularUser": "ผู้ใช้ทั่วไป", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "อนุญาตให้เข้าสู่ระบบทั้งจากภายนอกและโดยใช้รหัสผ่าน", "linkAccountTargetUsername": "ชื่อผู้ใช้บัญชีภายในเครื่อง", "linkAccountTargetPlaceholder": "ป้อนชื่อผู้ใช้บัญชีท้องถิ่นเพื่อเชื่อมโยง", - "linkAccountOidcUsername": "ชื่อผู้ใช้บัญชีภายนอก", - "linkAccountOidcPlaceholder": "ป้อนชื่อผู้ใช้บัญชีภายนอกเพื่อผสานรวม", - "linkAccountOidcNotFound": "ไม่พบบัญชีผู้ใช้ภายนอกที่มีชื่อผู้ใช้ดังกล่าว", + "linkAccountExternalUsername": "ชื่อผู้ใช้บัญชีภายนอก", + "linkAccountExternalPlaceholder": "ป้อนชื่อผู้ใช้บัญชีภายนอกเพื่อผสานรวม", + "linkAccountExternalNotFound": "ไม่พบบัญชีผู้ใช้ภายนอกที่มีชื่อผู้ใช้ดังกล่าว", "linkAccounts": "เชื่อมโยงบัญชี", "linkAccountSuccess": "เชื่อมโยงบัญชีสำเร็จแล้ว", "linkAccountFailed": "ไม่สามารถเชื่อมโยงบัญชีได้", @@ -1400,7 +1400,6 @@ "saving": "ประหยัด...", "updateRegistrationFailed": "ไม่สามารถอัปเดตการตั้งค่าการลงทะเบียนได้", "updatePasswordLoginFailed": "ไม่สามารถอัปเดตการตั้งค่าการเข้าสู่ระบบด้วยรหัสผ่านได้", - "updateOidcAutoProvisionFailed": "ไม่สามารถอัปเดตการตั้งค่าการสร้างอัตโนมัติได้", "updateSecondFactorAfterExternalLoginFailed": "ไม่สามารถอัปเดตการตั้งค่าปัจจัยยืนยันตัวตนที่สองได้", "updatePasswordResetFailed": "ไม่สามารถอัปเดตการตั้งค่าการรีเซ็ตรหัสผ่านได้", "sessionTimeoutRange2": "เวลาหมดอายุของเซสชันต้องอยู่ระหว่าง 1 ถึง 720 ชั่วโมง", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "ล็อกอยู่", "dataLockedNotice": "ข้อมูลของ {{username}}จะถูกล็อกไว้จนกว่าพวกเขาจะเข้าสู่ระบบครั้งถัดไป จะไม่สามารถดูหรือแก้ไขโฮสต์ ข้อมูลประจำตัว และข้อความย่อของพวกเขาได้จนกว่าจะถึงเวลานั้น", "resetPasswordTitle": "รีเซ็ตรหัสผ่าน", - "resetPasswordOidcOnly": "ผู้ใช้รายนี้เข้าสู่ระบบผ่านผู้ให้บริการภายนอกและไม่มีรหัสผ่าน", + "resetPasswordExternalOnly": "ผู้ใช้รายนี้เข้าสู่ระบบผ่านผู้ให้บริการภายนอกและไม่มีรหัสผ่าน", "resetPasswordPlaceholder": "รหัสผ่านใหม่", "resetPasswordBtn": "รีเซ็ต", "resetPasswordWorking": "กำลังรีเซ็ต...", @@ -1742,7 +1741,7 @@ "importFailed": "การนำเข้าล้มเหลว: {{error}}", "roleUser": "ผู้ใช้", "authMethodDual": "การตรวจสอบสิทธิ์แบบคู่", - "authMethodOidc": "ภายนอก", + "authMethodExternal": "ภายนอก", "done": "เสร็จแล้ว", "apiKeyNameRequired": "จำเป็นต้องระบุชื่อคีย์", "apiKeyCreated": "สร้างคีย์ API \"{{name}}\" แล้ว", diff --git a/src/ui/locales/translated/tr_TR.json b/src/ui/locales/translated/tr_TR.json index 295b6c99a..77f953210 100644 --- a/src/ui/locales/translated/tr_TR.json +++ b/src/ui/locales/translated/tr_TR.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "Klavye Etkileşimini Zorunlu Kıl", "overrideCredentialUsername": "Kimlik Kullanıcı Adını Geçersiz Kıl", "overrideCredentialUsernameDesc": "Kimlik bilgisinin kullanıcı adı yerine yukarıda belirtilen kullanıcı adını kullan", - "oidcUsernameHint": "SSO veya LDAP aracılığıyla oturum açtığınız adı girmek için $external.username ifadesini kullanın.", + "externalUsernameHint": "SSO veya LDAP aracılığıyla oturum açtığınız adı girmek için $external.username ifadesini kullanın.", "jumpHostChain": "Atlama Sunucu Zinciri", "portKnocking": "Port Vuruşu", "addKnock": "Port Ekle", @@ -977,7 +977,7 @@ "noSearchResults": "Aramanızla eşleşen bağlantı yok", "rename": "Oturumu yeniden adlandır" }, - "terminal": { + "sshAuth": { "totpRequired": "İki Faktörlü Kimlik Doğrulama Gerekli", "totpCodeLabel": "Doğrulama Kodu", "totpVerify": "Doğrula", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "Sıfırlama kodu doğrulanamadı", "failedCompleteReset": "Şifre sıfırlama tamamlanamadı", "invalidTotpCode": "Geçersiz TOTP kodu", - "failedOidcLogin": "Harici oturum açma işlemi başlatılamadı.", - "silentSigninOidcUnavailable": "Sessiz oturum açma isteği yapıldı, ancak harici oturum açma ayarı yapılmamış.", + "failedExternalLogin": "Harici oturum açma işlemi başlatılamadı.", + "silentSigninExternalUnavailable": "Sessiz oturum açma isteği yapıldı, ancak harici oturum açma ayarı yapılmamış.", "failedUserInfo": "Giriş sonrası kullanıcı bilgileri alınamadı", - "oidcAuthFailed": "Harici oturum açma başarısız oldu", + "externalAuthFailed": "Harici oturum açma başarısız oldu", "invalidAuthUrl": "Arka uçtan geçersiz yetkilendirme URL'si alındı", "requiredField": "Bu alan zorunludur", "minLength": "Minimum uzunluk {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "Yeni kullanıcıların kullanıcı adı ve parola ile kendilerini kaydetmesine izin ver", "allowPasswordLogin": "Parola ile Girişe İzin Ver", "allowPasswordLoginDesc": "Kullanıcı adı/parola ile giriş", - "oidcAutoProvision": "Harici hesapları otomatik olarak oluştur", - "oidcAutoProvisionDesc": "Kayıt kapalı olsa bile, SSO veya LDAP gibi harici bir giriş yöntemiyle ilk kez oturum açıldığında bir hesap oluşturulur.", + "externalAutoProvision": "Harici hesapları otomatik olarak oluştur", + "externalAutoProvisionDesc": "Kayıt kapalı olsa bile, SSO veya LDAP gibi harici bir giriş yöntemiyle ilk kez oturum açıldığında bir hesap oluşturulur.", "secondFactorAfterExternalLogin": "Harici oturum açma işlemlerinden sonra ikinci bir doğrulama faktörü isteyin (SSO, GitHub, Google, LDAP).", "secondFactorAfterExternalLoginDesc": "Kapalı modda, kayıtlı ikinci faktör yalnızca parola ve diğer yerel oturum açma işlemlerinden sonra istenir, tıpkı eskisi gibi. Açık modda ise, SSO ve LDAP dahil olmak üzere her oturum açma yönteminden sonra istenir.", "allowPasswordReset": "Parola Sıfırlamaya İzin Ver", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "Süresiz", "revokedBadge": "İPTAL EDİLDİ", "authTypeDual": "Çift Doğrulama", - "authTypeOidc": "Harici", + "authTypeExternal": "Harici", "authTypeLocal": "Yerel", "adminStatusAdministrator": "Yönetici", "adminStatusRegularUser": "Normal Kullanıcı", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "Hem harici giriş hem de parola ile giriş yapılmasına izin verin.", "linkAccountTargetUsername": "Yerel Hesap Kullanıcı Adı", "linkAccountTargetPlaceholder": "Bağlanacak yerel hesap kullanıcı adını girin", - "linkAccountOidcUsername": "Harici Hesap Kullanıcı Adı", - "linkAccountOidcPlaceholder": "Birleştirmek istediğiniz yalnızca harici hesap kullanıcı adını girin.", - "linkAccountOidcNotFound": "Bu kullanıcı adıyla harici erişime açık bir hesap bulunamadı.", + "linkAccountExternalUsername": "Harici Hesap Kullanıcı Adı", + "linkAccountExternalPlaceholder": "Birleştirmek istediğiniz yalnızca harici hesap kullanıcı adını girin.", + "linkAccountExternalNotFound": "Bu kullanıcı adıyla harici erişime açık bir hesap bulunamadı.", "linkAccounts": "Hesapları Bağla", "linkAccountSuccess": "Hesaplar başarıyla bağlandı", "linkAccountFailed": "Hesaplar bağlanamadı", @@ -1400,7 +1400,6 @@ "saving": "Kaydediliyor...", "updateRegistrationFailed": "Kayıt ayarı güncellenemedi", "updatePasswordLoginFailed": "Parola girişi ayarı güncellenemedi", - "updateOidcAutoProvisionFailed": "Otomatik oluşturma ayarı güncellenemedi.", "updateSecondFactorAfterExternalLoginFailed": "İkinci faktör ayarı güncellenemedi.", "updatePasswordResetFailed": "Parola sıfırlama ayarı güncellenemedi", "sessionTimeoutRange2": "Oturum zaman aşımı 1 ile 720 saat arasında olmalıdır", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "KİLİTLİ", "dataLockedNotice": "{{username}} kullanıcısının verileri, bir sonraki oturum açana kadar kilitli kalır. Bu süre zarfında ana makineleri, kimlik bilgileri ve kod parçacıkları görüntülenemez veya düzenlenemez.", "resetPasswordTitle": "Parolayı Sıfırla", - "resetPasswordOidcOnly": "Bu kullanıcı harici bir sağlayıcı üzerinden oturum açar ve parolası yoktur.", + "resetPasswordExternalOnly": "Bu kullanıcı harici bir sağlayıcı üzerinden oturum açar ve parolası yoktur.", "resetPasswordPlaceholder": "Yeni parola", "resetPasswordBtn": "Sıfırla", "resetPasswordWorking": "Sıfırlanıyor...", @@ -1742,7 +1741,7 @@ "importFailed": "İçe aktarma başarısız oldu: {{error}}", "roleUser": "Kullanıcı", "authMethodDual": "Çift Kimlik Doğrulama", - "authMethodOidc": "Harici", + "authMethodExternal": "Harici", "done": "Tamam", "apiKeyNameRequired": "Anahtar adı zorunludur", "apiKeyCreated": "API anahtarı \"{{name}}\" oluşturuldu", diff --git a/src/ui/locales/translated/uk_UA.json b/src/ui/locales/translated/uk_UA.json index 182df320e..ff44d525c 100644 --- a/src/ui/locales/translated/uk_UA.json +++ b/src/ui/locales/translated/uk_UA.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "Примусова інтерактивна клавіатура", "overrideCredentialUsername": "Перезаписати ім'я користувача облікових даних", "overrideCredentialUsernameDesc": "Використовуйте вказане вище ім'я користувача замість імені користувача облікових даних", - "oidcUsernameHint": "Використовуйте $external.username, щоб ввести ім’я користувача, яке ви використовували для входу через SSO або LDAP.", + "externalUsernameHint": "Використовуйте $external.username, щоб ввести ім’я користувача, яке ви використовували для входу через SSO або LDAP.", "jumpHostChain": "Ланцюг хоста стрибка", "portKnocking": "Порт-Нокінґ", "addKnock": "Додати порт", @@ -977,7 +977,7 @@ "noSearchResults": "Немає з'єднань, що відповідають вашому пошуку", "rename": "Перейменувати сеанс" }, - "terminal": { + "sshAuth": { "totpRequired": "Потрібна двофакторна автентифікація", "totpCodeLabel": "Код підтвердження", "totpVerify": "Перевірити", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "Не вдалося перевірити код скидання", "failedCompleteReset": "Не вдалося завершити скидання пароля", "invalidTotpCode": "Недійсний код TOTP", - "failedOidcLogin": "Не вдалося розпочати зовнішній вхід", - "silentSigninOidcUnavailable": "Було надіслано запит на тихий вхід, але зовнішній вхід не налаштовано.", + "failedExternalLogin": "Не вдалося розпочати зовнішній вхід", + "silentSigninExternalUnavailable": "Було надіслано запит на тихий вхід, але зовнішній вхід не налаштовано.", "failedUserInfo": "Не вдалося отримати інформацію про користувача після входу", - "oidcAuthFailed": "Зовнішній вхід не вдалося", + "externalAuthFailed": "Зовнішній вхід не вдалося", "invalidAuthUrl": "Недійсна URL-адреса авторизації отримана від серверної частини", "requiredField": "Це поле обов'язкове", "minLength": "Мінімальна довжина {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "Дозвольте новим користувачам самостійно реєструватися, використовуючи ім'я користувача та пароль", "allowPasswordLogin": "Дозволити вхід з паролем", "allowPasswordLoginDesc": "Вхід за ім'ям користувача/паролем", - "oidcAutoProvision": "Автоматичне створення зовнішніх облікових записів", - "oidcAutoProvisionDesc": "Створіть обліковий запис під час першого входу через зовнішній логін, такий як SSO або LDAP, навіть якщо реєстрацію вимкнено.", + "externalAutoProvision": "Автоматичне створення зовнішніх облікових записів", + "externalAutoProvisionDesc": "Створіть обліковий запис під час першого входу через зовнішній логін, такий як SSO або LDAP, навіть якщо реєстрацію вимкнено.", "secondFactorAfterExternalLogin": "Запитувати другий фактор після зовнішніх входів (SSO, GitHub, Google, LDAP)", "secondFactorAfterExternalLoginDesc": "Вимкнено, зареєстрований другий фактор запитується лише після пароля та інших локальних входів, як і раніше. Увімкнено, запитується після кожного методу входу, включаючи SSO та LDAP.", "allowPasswordReset": "Дозволити скидання пароля", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "Без терміну дії", "revokedBadge": "СКАСОВАНО", "authTypeDual": "Подвійна автентифікація", - "authTypeOidc": "Зовнішній", + "authTypeExternal": "Зовнішній", "authTypeLocal": "Місцевий", "adminStatusAdministrator": "Адміністратор", "adminStatusRegularUser": "Звичайний користувач", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "Дозволити як зовнішній вхід, так і вхід за паролем", "linkAccountTargetUsername": "Ім'я користувача локального облікового запису", "linkAccountTargetPlaceholder": "Введіть ім'я користувача локального облікового запису для підключення", - "linkAccountOidcUsername": "Ім'я користувача зовнішнього облікового запису", - "linkAccountOidcPlaceholder": "Введіть ім'я користувача зовнішнього облікового запису для об'єднання", - "linkAccountOidcNotFound": "Зовнішній обліковий запис із цим іменем користувача не знайдено", + "linkAccountExternalUsername": "Ім'я користувача зовнішнього облікового запису", + "linkAccountExternalPlaceholder": "Введіть ім'я користувача зовнішнього облікового запису для об'єднання", + "linkAccountExternalNotFound": "Зовнішній обліковий запис із цим іменем користувача не знайдено", "linkAccounts": "Зв'язати облікові записи", "linkAccountSuccess": "Облікові записи успішно пов’язано", "linkAccountFailed": "Не вдалося зв'язати облікові записи", @@ -1400,7 +1400,6 @@ "saving": "Збереження...", "updateRegistrationFailed": "Не вдалося оновити налаштування реєстрації", "updatePasswordLoginFailed": "Не вдалося оновити налаштування входу з паролем", - "updateOidcAutoProvisionFailed": "Не вдалося оновити налаштування автоматичного створення", "updateSecondFactorAfterExternalLoginFailed": "Не вдалося оновити налаштування другого фактора", "updatePasswordResetFailed": "Не вдалося оновити налаштування скидання пароля", "sessionTimeoutRange2": "Час очікування сеансу має бути від 1 до 720 годин", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "ЗАБЛОКОВАНО", "dataLockedNotice": "Дані {{username}}залишаються заблокованими до наступного входу в систему. До того часу їхні хости, облікові дані та фрагменти не можна переглядати або редагувати.", "resetPasswordTitle": "Скинути пароль", - "resetPasswordOidcOnly": "Цей користувач входить через зовнішнього постачальника та не має пароля.", + "resetPasswordExternalOnly": "Цей користувач входить через зовнішнього постачальника та не має пароля.", "resetPasswordPlaceholder": "Новий пароль", "resetPasswordBtn": "Скинути", "resetPasswordWorking": "Скидання...", @@ -1742,7 +1741,7 @@ "importFailed": "Помилка імпорту: {{error}}", "roleUser": "Користувач", "authMethodDual": "Подвійна автентифікація", - "authMethodOidc": "Зовнішній", + "authMethodExternal": "Зовнішній", "done": "Готово", "apiKeyNameRequired": "Потрібне ім'я ключа", "apiKeyCreated": "Ключ API \"{{name}}\" створено", diff --git a/src/ui/locales/translated/vi_VN.json b/src/ui/locales/translated/vi_VN.json index 3f4cc5702..bac4243cf 100644 --- a/src/ui/locales/translated/vi_VN.json +++ b/src/ui/locales/translated/vi_VN.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "Buộc dùng Keyboard-Interactive", "overrideCredentialUsername": "Ghi đè tên đăng nhập thông tin xác thực", "overrideCredentialUsernameDesc": "Sử dụng tên đăng nhập ở trên thay vì tên trong thông tin xác thực", - "oidcUsernameHint": "Sử dụng $external.username để điền tên bạn đã đăng nhập thông qua SSO hoặc LDAP.", + "externalUsernameHint": "Sử dụng $external.username để điền tên bạn đã đăng nhập thông qua SSO hoặc LDAP.", "jumpHostChain": "Chuỗi máy chủ trung gian", "portKnocking": "Port Knocking", "addKnock": "Thêm cổng", @@ -977,7 +977,7 @@ "noSearchResults": "Không có kết nối nào khớp với tìm kiếm của bạn", "rename": "Đổi tên phiên" }, - "terminal": { + "sshAuth": { "totpRequired": "Yêu cầu xác thực hai yếu tố", "totpCodeLabel": "Mã xác minh", "totpVerify": "Xác minh", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "Không thể xác minh mã đặt lại", "failedCompleteReset": "Không thể hoàn tất đặt lại mật khẩu", "invalidTotpCode": "Mã TOTP không hợp lệ", - "failedOidcLogin": "Đăng nhập bằng tài khoản bên ngoài không thành công.", - "silentSigninOidcUnavailable": "Đã yêu cầu đăng nhập im lặng, nhưng chưa thiết lập đăng nhập bằng tài khoản bên ngoài.", + "failedExternalLogin": "Đăng nhập bằng tài khoản bên ngoài không thành công.", + "silentSigninExternalUnavailable": "Đã yêu cầu đăng nhập im lặng, nhưng chưa thiết lập đăng nhập bằng tài khoản bên ngoài.", "failedUserInfo": "Không thể lấy thông tin người dùng sau khi đăng nhập", - "oidcAuthFailed": "Đăng nhập bên ngoài không thành công", + "externalAuthFailed": "Đăng nhập bên ngoài không thành công", "invalidAuthUrl": "URL ủy quyền không hợp lệ nhận được từ máy chủ", "requiredField": "Trường này là bắt buộc", "minLength": "Độ dài tối thiểu là {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "Cho phép người dùng mới tự đăng ký bằng tên người dùng và mật khẩu", "allowPasswordLogin": "Cho phép đăng nhập bằng mật khẩu", "allowPasswordLoginDesc": "Đăng nhập bằng tên người dùng/mật khẩu", - "oidcAutoProvision": "Tự động tạo tài khoản bên ngoài", - "oidcAutoProvisionDesc": "Tạo tài khoản lần đầu tiên khi ai đó đăng nhập thông qua phương thức đăng nhập bên ngoài như SSO hoặc LDAP, ngay cả khi tính năng đăng ký chưa được bật.", + "externalAutoProvision": "Tự động tạo tài khoản bên ngoài", + "externalAutoProvisionDesc": "Tạo tài khoản lần đầu tiên khi ai đó đăng nhập thông qua phương thức đăng nhập bên ngoài như SSO hoặc LDAP, ngay cả khi tính năng đăng ký chưa được bật.", "secondFactorAfterExternalLogin": "Hãy yêu cầu xác thực hai yếu tố sau khi đăng nhập bằng tài khoản bên ngoài (SSO, GitHub, Google, LDAP).", "secondFactorAfterExternalLoginDesc": "Ở chế độ Tắt, xác thực yếu tố thứ hai chỉ được yêu cầu sau khi nhập mật khẩu và các thông tin đăng nhập cục bộ khác, giống như trước đây. Ở chế độ Bật, xác thực yếu tố thứ hai sẽ được yêu cầu sau mọi phương thức đăng nhập, bao gồm cả SSO và LDAP.", "allowPasswordReset": "Cho phép đặt lại mật khẩu", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "Không hết hạn", "revokedBadge": "ĐÃ THU HỒI", "authTypeDual": "Xác thực kép", - "authTypeOidc": "Bên ngoài", + "authTypeExternal": "Bên ngoài", "authTypeLocal": "Cục bộ", "adminStatusAdministrator": "Quản trị viên", "adminStatusRegularUser": "Người dùng thông thường", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "Cho phép cả đăng nhập bằng tài khoản ngoài và đăng nhập bằng mật khẩu.", "linkAccountTargetUsername": "Tên đăng nhập tài khoản cục bộ", "linkAccountTargetPlaceholder": "Nhập tên đăng nhập tài khoản cục bộ để liên kết", - "linkAccountOidcUsername": "Tên người dùng tài khoản bên ngoài", - "linkAccountOidcPlaceholder": "Nhập tên người dùng tài khoản chỉ dùng bên ngoài để hợp nhất.", - "linkAccountOidcNotFound": "Không tìm thấy tài khoản chỉ dùng bên ngoài nào có tên người dùng đó.", + "linkAccountExternalUsername": "Tên người dùng tài khoản bên ngoài", + "linkAccountExternalPlaceholder": "Nhập tên người dùng tài khoản chỉ dùng bên ngoài để hợp nhất.", + "linkAccountExternalNotFound": "Không tìm thấy tài khoản chỉ dùng bên ngoài nào có tên người dùng đó.", "linkAccounts": "Liên kết tài khoản", "linkAccountSuccess": "Liên kết tài khoản thành công", "linkAccountFailed": "Liên kết tài khoản thất bại", @@ -1400,7 +1400,6 @@ "saving": "Đang lưu...", "updateRegistrationFailed": "Cập nhật cài đặt đăng ký thất bại", "updatePasswordLoginFailed": "Cập nhật cài đặt đăng nhập bằng mật khẩu thất bại", - "updateOidcAutoProvisionFailed": "Không thể cập nhật cài đặt tự động tạo.", "updateSecondFactorAfterExternalLoginFailed": "Không thể cập nhật cài đặt yếu tố xác thực thứ hai.", "updatePasswordResetFailed": "Cập nhật cài đặt đặt lại mật khẩu thất bại", "sessionTimeoutRange2": "Thời gian chờ phiên phải từ 1 đến 720 giờ", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "ĐÃ KHÓA", "dataLockedNotice": "Dữ liệu của {{username}} vẫn bị khóa cho đến khi họ đăng nhập lần tiếp theo. Máy chủ, thông tin đăng nhập và đoạn mã của họ không thể xem hoặc chỉnh sửa cho đến lúc đó.", "resetPasswordTitle": "Đặt lại mật khẩu", - "resetPasswordOidcOnly": "Người dùng này đăng nhập qua nhà cung cấp bên ngoài và không có mật khẩu.", + "resetPasswordExternalOnly": "Người dùng này đăng nhập qua nhà cung cấp bên ngoài và không có mật khẩu.", "resetPasswordPlaceholder": "Mật khẩu mới", "resetPasswordBtn": "Đặt lại", "resetPasswordWorking": "Đang đặt lại...", @@ -1742,7 +1741,7 @@ "importFailed": "Nhập khẩu thất bại: {{error}}", "roleUser": "Người dùng", "authMethodDual": "Xác thực kép", - "authMethodOidc": "Bên ngoài", + "authMethodExternal": "Bên ngoài", "done": "Xong", "apiKeyNameRequired": "Tên khóa là bắt buộc", "apiKeyCreated": "Đã tạo khóa API \"{{name}}\"", diff --git a/src/ui/locales/translated/zh_CN.json b/src/ui/locales/translated/zh_CN.json index 85b5d9f80..fa1e3d872 100644 --- a/src/ui/locales/translated/zh_CN.json +++ b/src/ui/locales/translated/zh_CN.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "强制键盘交互认证", "overrideCredentialUsername": "覆盖凭据用户名", "overrideCredentialUsernameDesc": "使用上方指定的用户名代替凭据中的用户名", - "oidcUsernameHint": "使用 $external.username 填写您通过 SSO 或 LDAP 登录的名称。", + "externalUsernameHint": "使用 $external.username 填写您通过 SSO 或 LDAP 登录的名称。", "jumpHostChain": "跳板主机链", "portKnocking": "端口敲门", "addKnock": "添加端口", @@ -977,7 +977,7 @@ "noSearchResults": "没有连接匹配您的搜索", "rename": "重命名会话" }, - "terminal": { + "sshAuth": { "totpRequired": "需要双因素认证", "totpCodeLabel": "验证码", "totpVerify": "验证", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "验证重置代码失败", "failedCompleteReset": "无法完成密码重置", "invalidTotpCode": "无效的 TOTP 代码", - "failedOidcLogin": "外部登录失败", - "silentSigninOidcUnavailable": "已请求静默登录,但未设置外部登录。", + "failedExternalLogin": "外部登录失败", + "silentSigninExternalUnavailable": "已请求静默登录,但未设置外部登录。", "failedUserInfo": "登录后无法获取用户信息", - "oidcAuthFailed": "外部登录失败", + "externalAuthFailed": "外部登录失败", "invalidAuthUrl": "从后端接收到无效的授权 URL", "requiredField": "此字段为必填项", "minLength": "最小长度为 {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "允许新用户使用用户名和密码自行注册", "allowPasswordLogin": "允许密码登录", "allowPasswordLoginDesc": "用户名/密码登录", - "oidcAutoProvision": "自动创建外部帐户", - "oidcAutoProvisionDesc": "即使注册功能已关闭,当用户首次通过外部登录(例如 SSO 或 LDAP)登录时,也应创建帐户。", + "externalAutoProvision": "自动创建外部帐户", + "externalAutoProvisionDesc": "即使注册功能已关闭,当用户首次通过外部登录(例如 SSO 或 LDAP)登录时,也应创建帐户。", "secondFactorAfterExternalLogin": "在外部登录(SSO、GitHub、Google、LDAP)后要求进行第二重验证", "secondFactorAfterExternalLoginDesc": "关闭双因素身份验证后,仅在密码登录和其他本地登录后才会要求输入已注册的双因素身份验证信息,与之前相同。开启双因素身份验证后,每次登录都会要求输入双因素身份验证信息,包括单点登录 (SSO) 和 LDAP 登录。", "allowPasswordReset": "允许密码重置", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "无过期", "revokedBadge": "已吊销", "authTypeDual": "双重认证", - "authTypeOidc": "外部", + "authTypeExternal": "外部", "authTypeLocal": "本地", "adminStatusAdministrator": "管理员", "adminStatusRegularUser": "普通用户", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "允许外部登录和密码登录", "linkAccountTargetUsername": "本地账号用户名", "linkAccountTargetPlaceholder": "输入要链接的本地账号用户名", - "linkAccountOidcUsername": "外部帐户用户名", - "linkAccountOidcPlaceholder": "输入仅供外部使用的帐户用户名以进行合并", - "linkAccountOidcNotFound": "未找到使用该用户名的仅限外部访问的帐户", + "linkAccountExternalUsername": "外部帐户用户名", + "linkAccountExternalPlaceholder": "输入仅供外部使用的帐户用户名以进行合并", + "linkAccountExternalNotFound": "未找到使用该用户名的仅限外部访问的帐户", "linkAccounts": "链接账号", "linkAccountSuccess": "账号已成功链接", "linkAccountFailed": "链接账号失败", @@ -1400,7 +1400,6 @@ "saving": "保存中...", "updateRegistrationFailed": "更新注册设置失败", "updatePasswordLoginFailed": "更新密码登录设置失败", - "updateOidcAutoProvisionFailed": "自动创建设置更新失败", "updateSecondFactorAfterExternalLoginFailed": "更新第二因素设置失败", "updatePasswordResetFailed": "更新密码重置设置失败", "sessionTimeoutRange2": "会话超时必须在 1 到 720 小时之间", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "已锁定", "dataLockedNotice": "{{username}} 的数据将保持锁定状态,直到其下次登录。在此之前,无法查看或编辑其主机、凭据和代码片段。", "resetPasswordTitle": "重置密码", - "resetPasswordOidcOnly": "此用户通过外部提供商登录,没有密码。", + "resetPasswordExternalOnly": "此用户通过外部提供商登录,没有密码。", "resetPasswordPlaceholder": "新密码", "resetPasswordBtn": "重置", "resetPasswordWorking": "正在重置...", @@ -1742,7 +1741,7 @@ "importFailed": "导入失败:{{error}}", "roleUser": "用户", "authMethodDual": "双重认证", - "authMethodOidc": "外部", + "authMethodExternal": "外部", "done": "完成", "apiKeyNameRequired": "密钥名称为必填项", "apiKeyCreated": "API 密钥 \"{{name}}\" 已创建", diff --git a/src/ui/locales/translated/zh_TW.json b/src/ui/locales/translated/zh_TW.json index e65fc0991..a80a15003 100644 --- a/src/ui/locales/translated/zh_TW.json +++ b/src/ui/locales/translated/zh_TW.json @@ -377,7 +377,7 @@ "forceKeyboardInteractive": "強制鍵盤互動式驗證", "overrideCredentialUsername": "覆寫憑證使用者名稱", "overrideCredentialUsernameDesc": "使用上方指定的使用者名稱,而非憑證中的使用者名稱", - "oidcUsernameHint": "使用 $external.username 填寫您透過 SSO 或 LDAP 登入的名稱。", + "externalUsernameHint": "使用 $external.username 填寫您透過 SSO 或 LDAP 登入的名稱。", "jumpHostChain": "跳板主機鏈", "portKnocking": "連接埠敲門", "addKnock": "新增連接埠", @@ -977,7 +977,7 @@ "noSearchResults": "無符合搜尋條件的連線", "rename": "重新命名工作階段" }, - "terminal": { + "sshAuth": { "totpRequired": "需要雙因素驗證", "totpCodeLabel": "驗證碼", "totpVerify": "驗證", @@ -1089,10 +1089,10 @@ "failedVerifyCode": "驗證重設碼失敗", "failedCompleteReset": "無法完成密碼重設", "invalidTotpCode": "無效的 TOTP 驗證碼", - "failedOidcLogin": "外部登入失敗", - "silentSigninOidcUnavailable": "已要求靜默登錄,但未設定外部登入。", + "failedExternalLogin": "外部登入失敗", + "silentSigninExternalUnavailable": "已要求靜默登錄,但未設定外部登入。", "failedUserInfo": "登入後無法取得使用者資訊", - "oidcAuthFailed": "外部登入失敗", + "externalAuthFailed": "外部登入失敗", "invalidAuthUrl": "從後端接收到無效的授權 URL", "requiredField": "此欄位為必填", "minLength": "最小長度為 {{min}}", @@ -1266,8 +1266,8 @@ "allowRegistrationDesc": "允許新使用者使用使用者名稱和密碼自行註冊", "allowPasswordLogin": "允許密碼登入", "allowPasswordLoginDesc": "使用者名稱/密碼登入", - "oidcAutoProvision": "自動建立外部帳戶", - "oidcAutoProvisionDesc": "即使註冊功能已關閉,當使用者首次透過外部登入(例如 SSO 或 LDAP)登入時,也應建立帳戶。", + "externalAutoProvision": "自動建立外部帳戶", + "externalAutoProvisionDesc": "即使註冊功能已關閉,當使用者首次透過外部登入(例如 SSO 或 LDAP)登入時,也應建立帳戶。", "secondFactorAfterExternalLogin": "在外部登入(SSO、GitHub、Google、LDAP)後要求進行第二重驗證", "secondFactorAfterExternalLoginDesc": "關閉雙重認證後,僅在密碼登入和其他本地登入後才會要求輸入已註冊的雙重認證訊息,與之前相同。開啟雙重認證後,每次登入都會要求輸入雙重認證訊息,包括單一登入 (SSO) 和 LDAP 登入。", "allowPasswordReset": "允許密碼重設", @@ -1324,7 +1324,7 @@ "apiKeyNoExpiry": "無到期日", "revokedBadge": "已撤銷", "authTypeDual": "雙重驗證", - "authTypeOidc": "外部", + "authTypeExternal": "外部", "authTypeLocal": "本機", "adminStatusAdministrator": "管理員", "adminStatusRegularUser": "一般使用者", @@ -1383,9 +1383,9 @@ "linkAccountEffect3": "允許外部登入和密碼登入", "linkAccountTargetUsername": "本機帳號使用者名稱", "linkAccountTargetPlaceholder": "輸入要連結到的本機帳號使用者名稱", - "linkAccountOidcUsername": "外部帳戶使用者名稱", - "linkAccountOidcPlaceholder": "輸入僅供外部使用的帳戶使用者名稱以進行合併", - "linkAccountOidcNotFound": "未找到使用該使用者名稱的僅限外部存取的帳戶", + "linkAccountExternalUsername": "外部帳戶使用者名稱", + "linkAccountExternalPlaceholder": "輸入僅供外部使用的帳戶使用者名稱以進行合併", + "linkAccountExternalNotFound": "未找到使用該使用者名稱的僅限外部存取的帳戶", "linkAccounts": "連結帳號", "linkAccountSuccess": "帳號連結成功", "linkAccountFailed": "帳號連結失敗", @@ -1400,7 +1400,6 @@ "saving": "儲存中...", "updateRegistrationFailed": "更新註冊設定失敗", "updatePasswordLoginFailed": "更新密碼登入設定失敗", - "updateOidcAutoProvisionFailed": "自動建立設定更新失敗", "updateSecondFactorAfterExternalLoginFailed": "更新第二因素設定失敗", "updatePasswordResetFailed": "更新密碼重設設定失敗", "sessionTimeoutRange2": "工作階段逾時必須介於 1 到 720 小時之間", @@ -1428,7 +1427,7 @@ "dataLockedBadge": "已鎖定", "dataLockedNotice": "{{username}} 的資料將保持鎖定,直到他們下次登入為止。在此之前無法檢視或編輯其主機、憑證和程式碼片段。", "resetPasswordTitle": "重設密碼", - "resetPasswordOidcOnly": "此使用者透過外部提供者登入,沒有密碼。", + "resetPasswordExternalOnly": "此使用者透過外部提供者登入,沒有密碼。", "resetPasswordPlaceholder": "新密碼", "resetPasswordBtn": "重設", "resetPasswordWorking": "重設中...", @@ -1742,7 +1741,7 @@ "importFailed": "導入失敗: {{error}}", "roleUser": "使用者", "authMethodDual": "雙重驗證", - "authMethodOidc": "外部", + "authMethodExternal": "外部", "done": "完成", "apiKeyNameRequired": "必須提供金鑰名稱", "apiKeyCreated": "API 金鑰「{{name}}」已建立", diff --git a/src/ui/main-axios.ts b/src/ui/main-axios.ts index 8c496f3d0..10f8407b3 100644 --- a/src/ui/main-axios.ts +++ b/src/ui/main-axios.ts @@ -77,8 +77,6 @@ export interface AuthResponse { username?: string; userId?: string; is_external?: boolean; - /** 2.8 name for is_external. */ - is_oidc?: boolean; totp_enabled?: boolean; requires_totp?: boolean; temp_token?: string; @@ -96,8 +94,6 @@ export interface UserInfo { is_admin: boolean; /** Signs in through an external login such as SSO or LDAP. */ is_external?: boolean; - /** 2.8 name for is_external. */ - is_oidc: boolean; is_dual_auth?: boolean; password_hash?: string; data_unlocked?: boolean; @@ -960,8 +956,7 @@ export async function loginUser( requires_totp: response.data.requires_totp, temp_token: response.data.temp_token, rememberMe: response.data.rememberMe, - is_external: response.data.is_external ?? response.data.is_oidc, - is_oidc: response.data.is_oidc, + is_external: response.data.is_external, totp_enabled: response.data.totp_enabled, token: response.data.token, }; diff --git a/src/ui/sidebar/AdminManagementSections.tsx b/src/ui/sidebar/AdminManagementSections.tsx index 6c2f08fb9..ffc3b207d 100644 --- a/src/ui/sidebar/AdminManagementSections.tsx +++ b/src/ui/sidebar/AdminManagementSections.tsx @@ -33,7 +33,7 @@ export type AdminUser = { id: string; username: string; isAdmin: boolean; - isOidc: boolean; + isExternal: boolean; passwordHash?: string; dataUnlocked?: boolean; secondFactorEnabled?: boolean; @@ -74,7 +74,7 @@ type UsersSectionProps = { setEditUserTarget: Dispatch>; setEditUserOpen: Dispatch>; setLinkAccountTarget: Dispatch< - SetStateAction<{ id: string; username: string; isOidc: boolean } | null> + SetStateAction<{ id: string; username: string; isExternal: boolean } | null> >; setLinkAccountOpen: Dispatch>; setUnlinkAccountTarget: Dispatch< @@ -158,10 +158,10 @@ export function AdminUsersSection({
{users.map((user) => { const authLabel = - user.isOidc && user.passwordHash + user.isExternal && user.passwordHash ? t("admin.authTypeDual") - : user.isOidc - ? t("admin.authTypeOidc") + : user.isExternal + ? t("admin.authTypeExternal") : t("admin.authTypeLocal"); return (
- {user.isOidc && user.passwordHash ? ( + {user.isExternal && user.passwordHash ? (
@@ -469,7 +469,11 @@ export function AdminUnlinkAccountDialog({ type LinkAccountDialogProps = { open: boolean; onOpenChange: Dispatch>; - linkAccountTarget: { id: string; username: string; isOidc: boolean } | null; + linkAccountTarget: { + id: string; + username: string; + isExternal: boolean; + } | null; setUsers: Dispatch>; users: AdminUser[]; }; @@ -489,7 +493,7 @@ export function AdminLinkAccountDialog({ if (open) setOtherUsername(""); }, [open, linkAccountTarget]); - const isOidcInitiator = linkAccountTarget?.isOidc ?? true; + const isExternalInitiator = linkAccountTarget?.isExternal ?? true; const handleSubmit = async () => { const trimmed = otherUsername.trim(); @@ -497,15 +501,15 @@ export function AdminLinkAccountDialog({ setSubmitting(true); try { - if (isOidcInitiator) { + if (isExternalInitiator) { await linkExternalToPasswordAccount(linkAccountTarget.id, trimmed); setUsers((prev) => prev.filter((u) => u.id !== linkAccountTarget.id)); } else { const oidcUser = users.find( - (u) => u.username === trimmed && u.isOidc && !u.passwordHash, + (u) => u.username === trimmed && u.isExternal && !u.passwordHash, ); if (!oidcUser) { - toast.error(t("admin.linkAccountOidcNotFound")); + toast.error(t("admin.linkAccountExternalNotFound")); return; } await linkExternalToPasswordAccount( @@ -532,7 +536,7 @@ export function AdminLinkAccountDialog({ {t("admin.linkAccountTitle")} - {isOidcInitiator + {isExternalInitiator ? t("admin.linkAccountDesc", { username: linkAccountTarget?.username, }) @@ -555,18 +559,18 @@ export function AdminLinkAccountDialog({
setOtherUsername(e.target.value)} placeholder={ - isOidcInitiator + isExternalInitiator ? t("admin.linkAccountTargetPlaceholder") - : t("admin.linkAccountOidcPlaceholder") + : t("admin.linkAccountExternalPlaceholder") } autoFocus disabled={submitting} diff --git a/src/ui/sidebar/AdminUserManagePanel.tsx b/src/ui/sidebar/AdminUserManagePanel.tsx index a37baf369..11279a049 100644 --- a/src/ui/sidebar/AdminUserManagePanel.tsx +++ b/src/ui/sidebar/AdminUserManagePanel.tsx @@ -450,9 +450,9 @@ export function AdminUserManagePanel({ {/* Password reset */}
{sectionHeading(t("admin.resetPasswordTitle"))} - {user.isOidc && !user.passwordHash ? ( + {user.isExternal && !user.passwordHash ? ( - {t("admin.resetPasswordOidcOnly")} + {t("admin.resetPasswordExternalOnly")} ) : (
diff --git a/src/ui/sidebar/HostEditor.tsx b/src/ui/sidebar/HostEditor.tsx index cfe69a2ff..d186a223c 100644 --- a/src/ui/sidebar/HostEditor.tsx +++ b/src/ui/sidebar/HostEditor.tsx @@ -182,7 +182,7 @@ export function HostEditor({ }; const [saving, setSaving] = useState(false); - const [isOidcUser, setIsOidcUser] = useState(false); + const [isExternalUser, setIsExternalUser] = useState(false); const [showSecretSources, setShowSecretSources] = useState(false); const [quickCredentialName, setQuickCredentialName] = useState(""); const [creatingQuickCredential, setCreatingQuickCredential] = useState(false); @@ -192,7 +192,7 @@ export function HostEditor({ useState(null); useEffect(() => { getUserInfo() - .then((info) => setIsOidcUser(info.is_external ?? info.is_oidc)) + .then((info) => setIsExternalUser(info.is_external)) .catch(() => {}); }, []); @@ -908,9 +908,9 @@ export function HostEditor({ }} onChange={(e) => setField("username", e.target.value)} /> - {isOidcUser && ( + {isExternalUser && (

- {t("hosts.oidcUsernameHint")} + {t("hosts.externalUsernameHint")}

)} {activeAuthEditor?.hintKey && ( diff --git a/src/ui/sidebar/UserProfilePanel.tsx b/src/ui/sidebar/UserProfilePanel.tsx index 4aebfa812..3f7a1186f 100644 --- a/src/ui/sidebar/UserProfilePanel.tsx +++ b/src/ui/sidebar/UserProfilePanel.tsx @@ -537,7 +537,7 @@ export function UserProfilePanel({ "up_to_date" | "requires_update" | "beta" | "unknown" >("up_to_date"); const [releaseUrl, setReleaseUrl] = useState(""); - const [isOidc, setIsOidc] = useState(false); + const [isExternal, setIsExternal] = useState(false); const [isDualAuth, setIsDualAuth] = useState(false); // Delete account @@ -647,7 +647,7 @@ export function UserProfilePanel({ getUserInfo() .then((info) => { setUserId(info.userId); - setIsOidc(info.is_external ?? info.is_oidc ?? false); + setIsExternal(info.is_external ?? false); setIsDualAuth(info.is_dual_auth ?? false); const linked = info.linked ?? null; setLinkedAccount(linked); @@ -661,8 +661,8 @@ export function UserProfilePanel({ ); if (info.is_dual_auth) { setAuthMethod(t("newUi.sidebar.userProfile.authMethodDual")); - } else if (info.is_external ?? info.is_oidc) { - setAuthMethod(t("newUi.sidebar.userProfile.authMethodOidc")); + } else if (info.is_external) { + setAuthMethod(t("newUi.sidebar.userProfile.authMethodExternal")); } else { setAuthMethod(t("newUi.sidebar.userProfile.authMethodLocal")); } @@ -1181,7 +1181,7 @@ export function UserProfilePanel({ } } - const canChangePasword = !isOidc || isDualAuth; + const canChangePasword = !isExternal || isDualAuth; return (
diff --git a/src/ui/ssh/dialogs/BrowserSignInDialog.tsx b/src/ui/ssh/dialogs/BrowserSignInDialog.tsx index 280bdb97a..c42b8dc57 100644 --- a/src/ui/ssh/dialogs/BrowserSignInDialog.tsx +++ b/src/ui/ssh/dialogs/BrowserSignInDialog.tsx @@ -60,7 +60,7 @@ export function BrowserSignInDialog({

- {t("terminal.browserSignInRequired", { provider: label })} + {t("sshAuth.browserSignInRequired", { provider: label })}

@@ -68,7 +68,7 @@ export function BrowserSignInDialog({ {code && code !== "N/A" && (

- {t("terminal.browserSignInCode")} + {t("sshAuth.browserSignInCode")}

@@ -80,7 +80,7 @@ export function BrowserSignInDialog({

- {t("terminal.browserSignInUrl")} + {t("sshAuth.browserSignInUrl")}

- {t("terminal.browserSignInContinue")} + {t("sshAuth.browserSignInContinue")}
diff --git a/src/ui/ssh/dialogs/TOTPDialog.tsx b/src/ui/ssh/dialogs/TOTPDialog.tsx index 243bf578b..d34a7d890 100644 --- a/src/ui/ssh/dialogs/TOTPDialog.tsx +++ b/src/ui/ssh/dialogs/TOTPDialog.tsx @@ -61,12 +61,12 @@ export function TOTPDialog({ }; const title = isPush - ? t("terminal.mfaPushRequired") + ? t("sshAuth.mfaPushRequired") : isMenu - ? t("terminal.mfaPromptRequired") - : t("terminal.totpRequired"); + ? t("sshAuth.mfaPromptRequired") + : t("sshAuth.totpRequired"); - const label = prompt || (isTotp ? t("terminal.totpCodeLabel") : undefined); + const label = prompt || (isTotp ? t("sshAuth.totpCodeLabel") : undefined); return (
@@ -93,7 +93,7 @@ export function TOTPDialog({

- {t("terminal.mfaWaitingApproval")} + {t("sshAuth.mfaWaitingApproval")}

@@ -115,7 +115,7 @@ export function TOTPDialog({ name="totpCode" type="text" autoFocus - placeholder={t("terminal.mfaMenuPlaceholder")} + placeholder={t("sshAuth.mfaMenuPlaceholder")} className="rounded-none bg-muted/50 border-border text-center text-sm tracking-widest" /> ) : allowsPushKeyword ? ( @@ -124,7 +124,7 @@ export function TOTPDialog({ name="totpCode" type="text" autoFocus - placeholder={t("terminal.mfaCodeOrPushPlaceholder")} + placeholder={t("sshAuth.mfaCodeOrPushPlaceholder")} className="rounded-none bg-muted/50 border-border text-center text-sm tracking-widest" /> ) : ( @@ -155,9 +155,7 @@ export function TOTPDialog({ variant="outline" className="border-accent-brand/40 text-accent-brand hover:bg-accent-brand/10 rounded-none text-[10px] font-bold uppercase tracking-widest" > - {isPush - ? t("terminal.mfaSendRequest") - : t("terminal.totpVerify")} + {isPush ? t("sshAuth.mfaSendRequest") : t("sshAuth.totpVerify")}
diff --git a/src/ui/tests/components/connection/ConnectionScreen.test.tsx b/src/ui/tests/components/connection/ConnectionScreen.test.tsx index 45713cf85..0e3160f64 100644 --- a/src/ui/tests/components/connection/ConnectionScreen.test.tsx +++ b/src/ui/tests/components/connection/ConnectionScreen.test.tsx @@ -47,6 +47,6 @@ describe("ConnectionScreen", () => { , ); - expect(screen.getByText(/terminal\.connectionLogTitle/)).toBeTruthy(); + expect(screen.getByText(/sshAuth\.connectionLogTitle/)).toBeTruthy(); }); }); diff --git a/src/ui/tests/sidebar/AdminUserManagePanel.test.tsx b/src/ui/tests/sidebar/AdminUserManagePanel.test.tsx index 83f830cfe..0d141507d 100644 --- a/src/ui/tests/sidebar/AdminUserManagePanel.test.tsx +++ b/src/ui/tests/sidebar/AdminUserManagePanel.test.tsx @@ -48,7 +48,7 @@ function makeUser(overrides: Partial = {}): AdminUser { id: "u2", username: "bob", isAdmin: false, - isOidc: false, + isExternal: false, passwordHash: "hash", dataUnlocked: true, ...overrides, diff --git a/src/ui/tests/types/ui-preferences.test.ts b/src/ui/tests/types/ui-preferences.test.ts index ff0c8609a..9314cf1d2 100644 --- a/src/ui/tests/types/ui-preferences.test.ts +++ b/src/ui/tests/types/ui-preferences.test.ts @@ -175,7 +175,7 @@ describe("PRESETS.balanced", () => { }); describe("plugin areas", () => { - it("moves version 1 docker and host metrics overrides to their plugins", () => { + it("drops 2.8 area names, even from an old version", () => { const upgraded = sanitizeUiPreferences({ version: 1, preset: "balanced", @@ -184,10 +184,7 @@ describe("plugin areas", () => { hostMetrics: { columns: 2 }, }, }); - expect(upgraded.overrides).toEqual({ - "plugin:docker": { containerLayout: "table" }, - "plugin:host-metrics": { columns: 2 }, - }); + expect(upgraded.overrides).toEqual({}); }); it("resolves a plugin area from its presets and the user's overrides", () => { @@ -220,24 +217,12 @@ describe("plugin areas", () => { }); describe("areas that moved into plugins", () => { - it("maps version 2 terminal and file manager overrides to their plugins", () => { + it("drops the old names", () => { expect( - sanitizeUiOverrides( - { - terminal: { toolbarDensity: "expanded" }, - fileManager: { viewMode: "list" }, - }, - 2, - ), - ).toEqual({ - "plugin:ssh-terminal": { toolbarDensity: "expanded" }, - "plugin:file-manager": { viewMode: "list" }, - }); - }); - - it("drops the old names from a current payload", () => { - expect( - sanitizeUiOverrides({ terminal: { toolbarDensity: "icon" } }), + sanitizeUiOverrides({ + terminal: { toolbarDensity: "icon" }, + fileManager: { viewMode: "list" }, + }), ).toEqual({}); }); });