From 6e56c90aac47d6a0d2f429c969dda703502ce902 Mon Sep 17 00:00:00 2001 From: LukeGus Date: Thu, 24 Sep 2026 20:24:09 -0500 Subject: [PATCH] feat: move vault ssh signing into a plugin (C5) --- docker/nginx-https.conf | 2 +- docker/nginx.conf | 2 +- drizzle/mysql/0054_wakeful_misty_knight.sql | 5 + drizzle/mysql/meta/0054_snapshot.json | 5014 +++++++++++++++++ drizzle/mysql/meta/_journal.json | 7 + drizzle/postgres/0055_jazzy_quasimodo.sql | 5 + drizzle/postgres/meta/0055_snapshot.json | 4913 ++++++++++++++++ drizzle/postgres/meta/_journal.json | 7 + drizzle/sqlite/0051_conscious_blur.sql | 5 + drizzle/sqlite/meta/0051_snapshot.json | 4731 ++++++++++++++++ drizzle/sqlite/meta/_journal.json | 7 + package-lock.json | 11 + packages/plugin-sdk/ARCHITECTURE.md | 52 +- packages/plugin-sdk/FINISH-LIST.md | 33 + plugins/file-manager/src/backend/index.ts | 6 - plugins/ssh-terminal/locales/en.json | 4 - .../locales/translated/af_ZA.json | 4 - .../locales/translated/ar_SA.json | 4 - .../locales/translated/bg_BG.json | 4 - .../locales/translated/bn_BD.json | 4 - .../locales/translated/ca_ES.json | 4 - .../locales/translated/cs_CZ.json | 4 - .../locales/translated/da_DK.json | 4 - .../locales/translated/de_DE.json | 4 - .../locales/translated/el_GR.json | 4 - .../locales/translated/es_ES.json | 4 - .../locales/translated/fi_FI.json | 4 - .../locales/translated/fr_FR.json | 4 - .../locales/translated/he_IL.json | 4 - .../locales/translated/hi_IN.json | 4 - .../locales/translated/hu_HU.json | 4 - .../locales/translated/id_ID.json | 4 - .../locales/translated/it_IT.json | 4 - .../locales/translated/ja_JP.json | 4 - .../locales/translated/ko_KR.json | 4 - .../locales/translated/nl_NL.json | 4 - .../locales/translated/no_NO.json | 4 - .../locales/translated/pl_PL.json | 4 - .../locales/translated/pt_BR.json | 4 - .../locales/translated/pt_PT.json | 4 - .../locales/translated/ro_RO.json | 4 - .../locales/translated/ru_RU.json | 4 - .../locales/translated/sr_SP.json | 4 - .../locales/translated/sv_SE.json | 4 - .../locales/translated/th_TH.json | 4 - .../locales/translated/tr_TR.json | 4 - .../locales/translated/uk_UA.json | 4 - .../locales/translated/vi_VN.json | 4 - .../locales/translated/zh_CN.json | 4 - .../locales/translated/zh_TW.json | 4 - .../src/backend/terminal-socket.ts | 2 +- .../src/frontend/terminal/Terminal.tsx | 155 - plugins/vault/CHANGELOG.md | 17 + plugins/vault/README.md | 29 + plugins/vault/locales/en.json | 69 + plugins/vault/locales/translated/af_ZA.json | 38 + plugins/vault/locales/translated/ar_SA.json | 38 + plugins/vault/locales/translated/bg_BG.json | 38 + plugins/vault/locales/translated/bn_BD.json | 38 + plugins/vault/locales/translated/ca_ES.json | 38 + plugins/vault/locales/translated/cs_CZ.json | 38 + plugins/vault/locales/translated/da_DK.json | 38 + plugins/vault/locales/translated/de_DE.json | 38 + plugins/vault/locales/translated/el_GR.json | 38 + plugins/vault/locales/translated/es_ES.json | 38 + plugins/vault/locales/translated/fi_FI.json | 38 + plugins/vault/locales/translated/fr_FR.json | 38 + plugins/vault/locales/translated/he_IL.json | 38 + plugins/vault/locales/translated/hi_IN.json | 38 + plugins/vault/locales/translated/hu_HU.json | 38 + plugins/vault/locales/translated/id_ID.json | 38 + plugins/vault/locales/translated/it_IT.json | 38 + plugins/vault/locales/translated/ja_JP.json | 38 + plugins/vault/locales/translated/ko_KR.json | 38 + plugins/vault/locales/translated/nl_NL.json | 38 + plugins/vault/locales/translated/no_NO.json | 38 + plugins/vault/locales/translated/pl_PL.json | 38 + plugins/vault/locales/translated/pt_BR.json | 38 + plugins/vault/locales/translated/pt_PT.json | 38 + plugins/vault/locales/translated/ro_RO.json | 38 + plugins/vault/locales/translated/ru_RU.json | 38 + plugins/vault/locales/translated/sr_SP.json | 38 + plugins/vault/locales/translated/sv_SE.json | 38 + plugins/vault/locales/translated/th_TH.json | 38 + plugins/vault/locales/translated/tr_TR.json | 38 + plugins/vault/locales/translated/uk_UA.json | 38 + plugins/vault/locales/translated/vi_VN.json | 38 + plugins/vault/locales/translated/zh_CN.json | 38 + plugins/vault/locales/translated/zh_TW.json | 38 + plugins/vault/manifest.json | 72 + .../mysql/0001_adopt_vault_tables.sql | 37 + .../postgres/0001_adopt_vault_tables.sql | 38 + plugins/vault/migrations/snapshot.json | 127 + .../sqlite/0001_adopt_vault_tables.sql | 38 + plugins/vault/package.json | 15 + plugins/vault/src/backend/auth-session.ts | 206 + plugins/vault/src/backend/index.ts | 41 + plugins/vault/src/backend/profile-store.ts | 139 + plugins/vault/src/backend/provider.ts | 124 + plugins/vault/src/backend/routes.ts | 337 ++ plugins/vault/src/backend/tables.ts | 74 + plugins/vault/src/backend/token-store.ts | 114 + .../vault/src/backend/vault-client.ts | 115 +- .../vault/src/frontend/RedirectUriSetting.tsx | 54 + .../vault/src/frontend/VaultAuthEditor.tsx | 108 + plugins/vault/src/frontend/VaultOverlay.tsx | 245 + .../src/frontend}/VaultProfileManager.tsx | 184 +- plugins/vault/src/frontend/index.tsx | 29 + plugins/vault/src/frontend/types.ts | 34 + plugins/vault/tests/backend/helpers.ts | 174 + .../vault/tests/backend/migrations.test.ts | 121 + .../vault/tests/backend/vault-client.test.ts | 176 +- plugins/vault/tests/backend/vault.test.ts | 471 ++ .../vault/tests/frontend/activate.test.tsx | 268 + plugins/vault/tsconfig.json | 4 + plugins/vault/vitest.config.ts | 3 + src/backend/auth/legacy-providers.ts | 132 - src/backend/auth/registry.ts | 4 +- src/backend/database/database.ts | 6 +- src/backend/database/db/index.ts | 68 - src/backend/database/db/schema.mysql.ts | 72 - src/backend/database/db/schema.pg.ts | 72 - src/backend/database/db/schema.ts | 72 - src/backend/database/repositories/factory.ts | 16 - .../host-resolution-repository.ts | 2 - .../repositories/vault-profile-repository.ts | 134 - .../repositories/vault-token-repository.ts | 126 - .../database/routes/delete-user-data.ts | 8 +- .../database/routes/host-compat-routes.ts | 23 +- .../database/routes/host-normalizers.ts | 1 - src/backend/database/routes/host.ts | 6 - src/backend/database/routes/sync-entities.ts | 13 +- .../database/routes/sync-references.ts | 3 +- src/backend/database/routes/vault.ts | 448 -- .../hosts/connect/auth-provider-registry.ts | 3 +- src/backend/hosts/connect/core-providers.ts | 2 - src/backend/hosts/connect/types.ts | 1 - src/backend/hosts/file-manager/index.ts | 10 - src/backend/hosts/host-resolver.ts | 21 - src/backend/hosts/vault-oidc-auth.ts | 214 - src/backend/hosts/vault-signer-auth.ts | 139 - src/backend/starter.ts | 4 + .../host-resolution-repository.test.ts | 1 - .../vault-profile-repository.test.ts | 115 - .../vault-token-repository.test.ts | 107 - .../routes/host-compat-routes.test.ts | 28 +- .../tests/database/routes/sync.test.ts | 1 - .../tests/database/sync-references.test.ts | 6 - .../tests/hosts/connect/pipeline.test.ts | 95 +- .../tests/hosts/host-resolver-sync-id.test.ts | 1 - src/backend/tests/hosts/host-resolver.test.ts | 11 +- .../tests/plugins/sync-registry.test.ts | 7 +- .../utils/vault-settings-migration.test.ts | 152 + .../vault-settings-migration.ts | 149 + src/backend/utils/field-crypto.ts | 1 - src/types/index.ts | 3 - src/types/ui-types.ts | 21 - src/ui/api/vault-profiles-api.ts | 60 - src/ui/locales/en.json | 24 - src/ui/locales/translated/af_ZA.json | 24 - src/ui/locales/translated/ar_SA.json | 24 - src/ui/locales/translated/bg_BG.json | 24 - src/ui/locales/translated/bn_BD.json | 24 - src/ui/locales/translated/ca_ES.json | 24 - src/ui/locales/translated/cs_CZ.json | 24 - src/ui/locales/translated/da_DK.json | 24 - src/ui/locales/translated/de_DE.json | 24 - src/ui/locales/translated/el_GR.json | 24 - src/ui/locales/translated/es_ES.json | 24 - src/ui/locales/translated/fi_FI.json | 24 - src/ui/locales/translated/fr_FR.json | 24 - src/ui/locales/translated/he_IL.json | 24 - src/ui/locales/translated/hi_IN.json | 24 - src/ui/locales/translated/hu_HU.json | 24 - src/ui/locales/translated/id_ID.json | 24 - src/ui/locales/translated/it_IT.json | 24 - src/ui/locales/translated/ja_JP.json | 24 - src/ui/locales/translated/ko_KR.json | 24 - src/ui/locales/translated/nl_NL.json | 24 - src/ui/locales/translated/no_NO.json | 24 - src/ui/locales/translated/pl_PL.json | 24 - src/ui/locales/translated/pt_BR.json | 24 - src/ui/locales/translated/pt_PT.json | 24 - src/ui/locales/translated/ro_RO.json | 24 - src/ui/locales/translated/ru_RU.json | 24 - src/ui/locales/translated/sr_SP.json | 24 - src/ui/locales/translated/sv_SE.json | 24 - src/ui/locales/translated/th_TH.json | 24 - src/ui/locales/translated/tr_TR.json | 24 - src/ui/locales/translated/uk_UA.json | 24 - src/ui/locales/translated/vi_VN.json | 24 - src/ui/locales/translated/zh_CN.json | 24 - src/ui/locales/translated/zh_TW.json | 24 - src/ui/main-axios.ts | 8 - src/ui/sidebar/HostEditor.tsx | 67 +- src/ui/sidebar/HostEditorData.ts | 5 - src/ui/sidebar/HostManagerData.ts | 4 - src/ui/tests/sidebar/HostEditorData.test.ts | 28 - 198 files changed, 19717 insertions(+), 3445 deletions(-) create mode 100644 drizzle/mysql/0054_wakeful_misty_knight.sql create mode 100644 drizzle/mysql/meta/0054_snapshot.json create mode 100644 drizzle/postgres/0055_jazzy_quasimodo.sql create mode 100644 drizzle/postgres/meta/0055_snapshot.json create mode 100644 drizzle/sqlite/0051_conscious_blur.sql create mode 100644 drizzle/sqlite/meta/0051_snapshot.json create mode 100644 plugins/vault/CHANGELOG.md create mode 100644 plugins/vault/README.md create mode 100644 plugins/vault/locales/en.json create mode 100644 plugins/vault/locales/translated/af_ZA.json create mode 100644 plugins/vault/locales/translated/ar_SA.json create mode 100644 plugins/vault/locales/translated/bg_BG.json create mode 100644 plugins/vault/locales/translated/bn_BD.json create mode 100644 plugins/vault/locales/translated/ca_ES.json create mode 100644 plugins/vault/locales/translated/cs_CZ.json create mode 100644 plugins/vault/locales/translated/da_DK.json create mode 100644 plugins/vault/locales/translated/de_DE.json create mode 100644 plugins/vault/locales/translated/el_GR.json create mode 100644 plugins/vault/locales/translated/es_ES.json create mode 100644 plugins/vault/locales/translated/fi_FI.json create mode 100644 plugins/vault/locales/translated/fr_FR.json create mode 100644 plugins/vault/locales/translated/he_IL.json create mode 100644 plugins/vault/locales/translated/hi_IN.json create mode 100644 plugins/vault/locales/translated/hu_HU.json create mode 100644 plugins/vault/locales/translated/id_ID.json create mode 100644 plugins/vault/locales/translated/it_IT.json create mode 100644 plugins/vault/locales/translated/ja_JP.json create mode 100644 plugins/vault/locales/translated/ko_KR.json create mode 100644 plugins/vault/locales/translated/nl_NL.json create mode 100644 plugins/vault/locales/translated/no_NO.json create mode 100644 plugins/vault/locales/translated/pl_PL.json create mode 100644 plugins/vault/locales/translated/pt_BR.json create mode 100644 plugins/vault/locales/translated/pt_PT.json create mode 100644 plugins/vault/locales/translated/ro_RO.json create mode 100644 plugins/vault/locales/translated/ru_RU.json create mode 100644 plugins/vault/locales/translated/sr_SP.json create mode 100644 plugins/vault/locales/translated/sv_SE.json create mode 100644 plugins/vault/locales/translated/th_TH.json create mode 100644 plugins/vault/locales/translated/tr_TR.json create mode 100644 plugins/vault/locales/translated/uk_UA.json create mode 100644 plugins/vault/locales/translated/vi_VN.json create mode 100644 plugins/vault/locales/translated/zh_CN.json create mode 100644 plugins/vault/locales/translated/zh_TW.json create mode 100644 plugins/vault/manifest.json create mode 100644 plugins/vault/migrations/mysql/0001_adopt_vault_tables.sql create mode 100644 plugins/vault/migrations/postgres/0001_adopt_vault_tables.sql create mode 100644 plugins/vault/migrations/snapshot.json create mode 100644 plugins/vault/migrations/sqlite/0001_adopt_vault_tables.sql create mode 100644 plugins/vault/package.json create mode 100644 plugins/vault/src/backend/auth-session.ts create mode 100644 plugins/vault/src/backend/index.ts create mode 100644 plugins/vault/src/backend/profile-store.ts create mode 100644 plugins/vault/src/backend/provider.ts create mode 100644 plugins/vault/src/backend/routes.ts create mode 100644 plugins/vault/src/backend/tables.ts create mode 100644 plugins/vault/src/backend/token-store.ts rename src/backend/hosts/vault-signer-core.ts => plugins/vault/src/backend/vault-client.ts (71%) create mode 100644 plugins/vault/src/frontend/RedirectUriSetting.tsx create mode 100644 plugins/vault/src/frontend/VaultAuthEditor.tsx create mode 100644 plugins/vault/src/frontend/VaultOverlay.tsx rename {src/ui/sidebar => plugins/vault/src/frontend}/VaultProfileManager.tsx (56%) create mode 100644 plugins/vault/src/frontend/index.tsx create mode 100644 plugins/vault/src/frontend/types.ts create mode 100644 plugins/vault/tests/backend/helpers.ts create mode 100644 plugins/vault/tests/backend/migrations.test.ts rename src/backend/tests/hosts/vault-signer-core.test.ts => plugins/vault/tests/backend/vault-client.test.ts (50%) create mode 100644 plugins/vault/tests/backend/vault.test.ts create mode 100644 plugins/vault/tests/frontend/activate.test.tsx create mode 100644 plugins/vault/tsconfig.json create mode 100644 plugins/vault/vitest.config.ts delete mode 100644 src/backend/auth/legacy-providers.ts delete mode 100644 src/backend/database/repositories/vault-profile-repository.ts delete mode 100644 src/backend/database/repositories/vault-token-repository.ts delete mode 100644 src/backend/database/routes/vault.ts delete mode 100644 src/backend/hosts/vault-oidc-auth.ts delete mode 100644 src/backend/hosts/vault-signer-auth.ts delete mode 100644 src/backend/tests/database/repositories/vault-profile-repository.test.ts delete mode 100644 src/backend/tests/database/repositories/vault-token-repository.test.ts create mode 100644 src/backend/tests/utils/vault-settings-migration.test.ts create mode 100644 src/backend/utils/crypto-migration/vault-settings-migration.ts delete mode 100644 src/ui/api/vault-profiles-api.ts diff --git a/docker/nginx-https.conf b/docker/nginx-https.conf index 5c37bce8e..6a69afcc6 100644 --- a/docker/nginx-https.conf +++ b/docker/nginx-https.conf @@ -267,7 +267,7 @@ http { proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; } - location ~ ^/vault(/.*)?$ { + location = /vault/oidc/callback { proxy_pass http://127.0.0.1:30001; proxy_http_version 1.1; proxy_set_header Host $http_host; diff --git a/docker/nginx.conf b/docker/nginx.conf index 3440684ee..642e0a53a 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -247,7 +247,7 @@ http { proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; } - location ~ ^/vault(/.*)?$ { + location = /vault/oidc/callback { proxy_pass http://127.0.0.1:30001; proxy_http_version 1.1; proxy_set_header Host $http_host; diff --git a/drizzle/mysql/0054_wakeful_misty_knight.sql b/drizzle/mysql/0054_wakeful_misty_knight.sql new file mode 100644 index 000000000..5894e734e --- /dev/null +++ b/drizzle/mysql/0054_wakeful_misty_knight.sql @@ -0,0 +1,5 @@ +-- vault_profiles and vault_tokens move to the vault plugin's adopted tables +-- at activation, and ssh_data.vault_profile_id is copied into the vault +-- plugin's host settings by a boot migration after this runs. Dropping any +-- of them here would lose the data first. +SELECT 1; \ No newline at end of file diff --git a/drizzle/mysql/meta/0054_snapshot.json b/drizzle/mysql/meta/0054_snapshot.json new file mode 100644 index 000000000..e234a8119 --- /dev/null +++ b/drizzle/mysql/meta/0054_snapshot.json @@ -0,0 +1,5014 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "6ee65e07-4933-4803-af2a-716c509e2831", + "prevId": "eabd98bd-d70d-4ec1-967a-0738e74cd55c", + "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": "varchar(255)", + "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": "varchar(255)", + "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": "varchar(255)", + "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": {} + }, + "dismissed_alerts": { + "name": "dismissed_alerts", + "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 + }, + "alert_id": { + "name": "alert_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "idx_dismissed_alerts_user_id": { + "name": "idx_dismissed_alerts_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "dismissed_alerts_user_id_users_id_fk": { + "name": "dismissed_alerts_user_id_users_id_fk", + "tableFrom": "dismissed_alerts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "dismissed_alerts_id": { + "name": "dismissed_alerts_id", + "columns": [ + "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": "varchar(255)", + "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_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": "varchar(255)", + "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": "varchar(255)", + "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 + }, + "autostart_password": { + "name": "autostart_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "autostart_key": { + "name": "autostart_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "autostart_key_password": { + "name": "autostart_key_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 + }, + "enable_terminal": { + "name": "enable_terminal", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "enable_session_logging": { + "name": "enable_session_logging", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "allow_session_sharing": { + "name": "allow_session_sharing", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "enable_command_history": { + "name": "enable_command_history", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "enable_tunnel": { + "name": "enable_tunnel", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "tunnel_connections": { + "name": "tunnel_connections", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "jump_hosts": { + "name": "jump_hosts", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enable_file_manager": { + "name": "enable_file_manager", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "scp_legacy": { + "name": "scp_legacy", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "enable_web_ui": { + "name": "enable_web_ui", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "enable_tmux_monitor": { + "name": "enable_tmux_monitor", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "enable_terminal_toolbar": { + "name": "enable_terminal_toolbar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "show_terminal_in_sidebar": { + "name": "show_terminal_in_sidebar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "show_file_manager_in_sidebar": { + "name": "show_file_manager_in_sidebar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "show_tunnel_in_sidebar": { + "name": "show_tunnel_in_sidebar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "show_docker_in_sidebar": { + "name": "show_docker_in_sidebar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "show_server_stats_in_sidebar": { + "name": "show_server_stats_in_sidebar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "default_path": { + "name": "default_path", + "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 + }, + "web_ui_config": { + "name": "web_ui_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_config": { + "name": "terminal_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "quick_actions": { + "name": "quick_actions", + "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 + }, + "rdp_credential_id": { + "name": "rdp_credential_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_user": { + "name": "rdp_user", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_password": { + "name": "rdp_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_domain": { + "name": "rdp_domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vnc_credential_id": { + "name": "vnc_credential_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vnc_password": { + "name": "vnc_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vnc_user": { + "name": "vnc_user", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "telnet_user": { + "name": "telnet_user", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "telnet_password": { + "name": "telnet_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "telnet_credential_id": { + "name": "telnet_credential_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_auth_type": { + "name": "rdp_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vnc_auth_type": { + "name": "vnc_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "telnet_auth_type": { + "name": "telnet_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "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 + }, + "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" + }, + "ssh_data_rdp_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_rdp_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "rdp_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_vnc_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_vnc_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "vnc_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_telnet_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_telnet_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "telnet_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": {} + }, + "notification_channels": { + "name": "notification_channels", + "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": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "notification_channels_user_id_users_id_fk": { + "name": "notification_channels_user_id_users_id_fk", + "tableFrom": "notification_channels", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "notification_channels_id": { + "name": "notification_channels_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "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": "varchar(255)", + "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": "varchar(255)", + "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": "varchar(255)", + "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": "varchar(255)", + "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 + }, + "oidc_sub": { + "name": "oidc_sub", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "oidc_sid": { + "name": "oidc_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sso_provider_id": { + "name": "sso_provider_id", + "type": "int", + "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 + }, + "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": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "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 + }, + "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": "varchar(255)", + "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 + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "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 + }, + "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" + }, + "ssh_folders_credential_id_ssh_credentials_id_fk": { + "name": "ssh_folders_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_folders", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "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_tombstones": { + "name": "sync_tombstones", + "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": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "sync_tombstones_user_id_users_id_fk": { + "name": "sync_tombstones_user_id_users_id_fk", + "tableFrom": "sync_tombstones", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "sync_tombstones_id": { + "name": "sync_tombstones_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "termix_identities": { + "name": "termix_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 + }, + "handle": { + "name": "handle", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "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": { + "termix_identities_user_id_users_id_fk": { + "name": "termix_identities_user_id_users_id_fk", + "tableFrom": "termix_identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "termix_identities_id": { + "name": "termix_identities_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "termix_identities_user_id_unique": { + "name": "termix_identities_user_id_unique", + "columns": [ + "user_id" + ] + }, + "termix_identities_handle_unique": { + "name": "termix_identities_handle_unique", + "columns": [ + "handle" + ] + } + }, + "checkConstraint": {} + }, + "termix_identity_ca": { + "name": "termix_identity_ca", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "identity_id": { + "name": "identity_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "validity_days": { + "name": "validity_days", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 90 + }, + "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": { + "termix_identity_ca_identity_id_termix_identities_id_fk": { + "name": "termix_identity_ca_identity_id_termix_identities_id_fk", + "tableFrom": "termix_identity_ca", + "tableTo": "termix_identities", + "columnsFrom": [ + "identity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_ca_user_id_users_id_fk": { + "name": "termix_identity_ca_user_id_users_id_fk", + "tableFrom": "termix_identity_ca", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "termix_identity_ca_id": { + "name": "termix_identity_ca_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "termix_identity_ca_identity_id_unique": { + "name": "termix_identity_ca_identity_id_unique", + "columns": [ + "identity_id" + ] + } + }, + "checkConstraint": {} + }, + "termix_identity_keys": { + "name": "termix_identity_keys", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "identity_id": { + "name": "identity_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "algorithm": { + "name": "algorithm", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('manual')" + }, + "credential_id": { + "name": "credential_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": {}, + "foreignKeys": { + "termix_identity_keys_identity_id_termix_identities_id_fk": { + "name": "termix_identity_keys_identity_id_termix_identities_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "termix_identities", + "columnsFrom": [ + "identity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_keys_user_id_users_id_fk": { + "name": "termix_identity_keys_user_id_users_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_keys_credential_id_ssh_credentials_id_fk": { + "name": "termix_identity_keys_credential_id_ssh_credentials_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "termix_identity_keys_id": { + "name": "termix_identity_keys_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": "varchar(255)", + "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_autocomplete": { + "name": "command_autocomplete", + "type": "boolean", + "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 + }, + "confirm_snippet_execution": { + "name": "confirm_snippet_execution", + "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_themes": { + "name": "custom_themes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "custom_keybindings": { + "name": "custom_keybindings", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_defaults": { + "name": "terminal_defaults", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_macros": { + "name": "terminal_macros", + "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 + }, + "is_oidc": { + "name": "is_oidc", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "oidc_identifier": { + "name": "oidc_identifier", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sso_provider_id": { + "name": "sso_provider_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "issuer_url": { + "name": "issuer_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "authorization_url": { + "name": "authorization_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_url": { + "name": "token_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "identifier_path": { + "name": "identifier_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name_path": { + "name": "name_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "('openid email profile')" + }, + "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 bf5595790..c9dd12183 100644 --- a/drizzle/mysql/meta/_journal.json +++ b/drizzle/mysql/meta/_journal.json @@ -379,6 +379,13 @@ "when": 1790295071302, "tag": "0053_magical_groot", "breakpoints": true + }, + { + "idx": 54, + "version": "5", + "when": 1790298804983, + "tag": "0054_wakeful_misty_knight", + "breakpoints": true } ] } \ No newline at end of file diff --git a/drizzle/postgres/0055_jazzy_quasimodo.sql b/drizzle/postgres/0055_jazzy_quasimodo.sql new file mode 100644 index 000000000..5894e734e --- /dev/null +++ b/drizzle/postgres/0055_jazzy_quasimodo.sql @@ -0,0 +1,5 @@ +-- vault_profiles and vault_tokens move to the vault plugin's adopted tables +-- at activation, and ssh_data.vault_profile_id is copied into the vault +-- plugin's host settings by a boot migration after this runs. Dropping any +-- of them here would lose the data first. +SELECT 1; \ No newline at end of file diff --git a/drizzle/postgres/meta/0055_snapshot.json b/drizzle/postgres/meta/0055_snapshot.json new file mode 100644 index 000000000..adcf1798b --- /dev/null +++ b/drizzle/postgres/meta/0055_snapshot.json @@ -0,0 +1,4913 @@ +{ + "id": "f1bfe6c6-1b07-4507-8dcb-8bbdb9434f51", + "prevId": "9363d178-2170-4694-bec9-7e891171c861", + "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": "varchar(255)", + "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": "varchar(255)", + "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": "varchar(255)", + "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.dismissed_alerts": { + "name": "dismissed_alerts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "alert_id": { + "name": "alert_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_dismissed_alerts_user_id": { + "name": "idx_dismissed_alerts_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dismissed_alerts_user_id_users_id_fk": { + "name": "dismissed_alerts_user_id_users_id_fk", + "tableFrom": "dismissed_alerts", + "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": "varchar(255)", + "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_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": "varchar(255)", + "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": "varchar(255)", + "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 + }, + "autostart_password": { + "name": "autostart_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "autostart_key": { + "name": "autostart_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "autostart_key_password": { + "name": "autostart_key_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 + }, + "enable_terminal": { + "name": "enable_terminal", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_session_logging": { + "name": "enable_session_logging", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "allow_session_sharing": { + "name": "allow_session_sharing", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_command_history": { + "name": "enable_command_history", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_tunnel": { + "name": "enable_tunnel", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "tunnel_connections": { + "name": "tunnel_connections", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "jump_hosts": { + "name": "jump_hosts", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enable_file_manager": { + "name": "enable_file_manager", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "scp_legacy": { + "name": "scp_legacy", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_web_ui": { + "name": "enable_web_ui", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_tmux_monitor": { + "name": "enable_tmux_monitor", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_terminal_toolbar": { + "name": "enable_terminal_toolbar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "show_terminal_in_sidebar": { + "name": "show_terminal_in_sidebar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "show_file_manager_in_sidebar": { + "name": "show_file_manager_in_sidebar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "show_tunnel_in_sidebar": { + "name": "show_tunnel_in_sidebar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "show_docker_in_sidebar": { + "name": "show_docker_in_sidebar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "show_server_stats_in_sidebar": { + "name": "show_server_stats_in_sidebar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default_path": { + "name": "default_path", + "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 + }, + "web_ui_config": { + "name": "web_ui_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "terminal_config": { + "name": "terminal_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quick_actions": { + "name": "quick_actions", + "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 + }, + "rdp_credential_id": { + "name": "rdp_credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "rdp_user": { + "name": "rdp_user", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rdp_password": { + "name": "rdp_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rdp_domain": { + "name": "rdp_domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "vnc_credential_id": { + "name": "vnc_credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "vnc_password": { + "name": "vnc_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "vnc_user": { + "name": "vnc_user", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "telnet_user": { + "name": "telnet_user", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "telnet_password": { + "name": "telnet_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "telnet_credential_id": { + "name": "telnet_credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "rdp_auth_type": { + "name": "rdp_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "vnc_auth_type": { + "name": "vnc_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "telnet_auth_type": { + "name": "telnet_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "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" + }, + "ssh_data_rdp_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_rdp_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "rdp_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_vnc_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_vnc_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "vnc_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_telnet_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_telnet_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "telnet_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.notification_channels": { + "name": "notification_channels", + "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": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "notification_channels_user_id_users_id_fk": { + "name": "notification_channels_user_id_users_id_fk", + "tableFrom": "notification_channels", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "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": "varchar(255)", + "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": "varchar(255)", + "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": "varchar(255)", + "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 + }, + "oidc_sub": { + "name": "oidc_sub", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oidc_sid": { + "name": "oidc_sid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sso_provider_id": { + "name": "sso_provider_id", + "type": "integer", + "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 + }, + "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": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "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 + }, + "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": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "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 + }, + "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" + }, + "ssh_folders_credential_id_ssh_credentials_id_fk": { + "name": "ssh_folders_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_folders", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "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_tombstones": { + "name": "sync_tombstones", + "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": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "sync_tombstones_user_id_users_id_fk": { + "name": "sync_tombstones_user_id_users_id_fk", + "tableFrom": "sync_tombstones", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.termix_identities": { + "name": "termix_identities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "handle": { + "name": "handle", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "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": { + "termix_identities_user_id_users_id_fk": { + "name": "termix_identities_user_id_users_id_fk", + "tableFrom": "termix_identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "termix_identities_user_id_unique": { + "name": "termix_identities_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + }, + "termix_identities_handle_unique": { + "name": "termix_identities_handle_unique", + "nullsNotDistinct": false, + "columns": [ + "handle" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.termix_identity_ca": { + "name": "termix_identity_ca", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "identity_id": { + "name": "identity_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "validity_days": { + "name": "validity_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 90 + }, + "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": { + "termix_identity_ca_identity_id_termix_identities_id_fk": { + "name": "termix_identity_ca_identity_id_termix_identities_id_fk", + "tableFrom": "termix_identity_ca", + "tableTo": "termix_identities", + "columnsFrom": [ + "identity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_ca_user_id_users_id_fk": { + "name": "termix_identity_ca_user_id_users_id_fk", + "tableFrom": "termix_identity_ca", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "termix_identity_ca_identity_id_unique": { + "name": "termix_identity_ca_identity_id_unique", + "nullsNotDistinct": false, + "columns": [ + "identity_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.termix_identity_keys": { + "name": "termix_identity_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "identity_id": { + "name": "identity_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "algorithm": { + "name": "algorithm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "termix_identity_keys_identity_id_termix_identities_id_fk": { + "name": "termix_identity_keys_identity_id_termix_identities_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "termix_identities", + "columnsFrom": [ + "identity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_keys_user_id_users_id_fk": { + "name": "termix_identity_keys_user_id_users_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_keys_credential_id_ssh_credentials_id_fk": { + "name": "termix_identity_keys_credential_id_ssh_credentials_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "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": "varchar(255)", + "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_autocomplete": { + "name": "command_autocomplete", + "type": "boolean", + "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 + }, + "confirm_snippet_execution": { + "name": "confirm_snippet_execution", + "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_themes": { + "name": "custom_themes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "custom_keybindings": { + "name": "custom_keybindings", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "terminal_defaults": { + "name": "terminal_defaults", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "terminal_macros": { + "name": "terminal_macros", + "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 + }, + "is_oidc": { + "name": "is_oidc", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "oidc_identifier": { + "name": "oidc_identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sso_provider_id": { + "name": "sso_provider_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issuer_url": { + "name": "issuer_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "authorization_url": { + "name": "authorization_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_url": { + "name": "token_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "identifier_path": { + "name": "identifier_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name_path": { + "name": "name_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'openid email profile'" + }, + "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 7bfadc151..5c310adc3 100644 --- a/drizzle/postgres/meta/_journal.json +++ b/drizzle/postgres/meta/_journal.json @@ -386,6 +386,13 @@ "when": 1790295070561, "tag": "0054_cloudy_galactus", "breakpoints": true + }, + { + "idx": 55, + "version": "7", + "when": 1790298804216, + "tag": "0055_jazzy_quasimodo", + "breakpoints": true } ] } \ No newline at end of file diff --git a/drizzle/sqlite/0051_conscious_blur.sql b/drizzle/sqlite/0051_conscious_blur.sql new file mode 100644 index 000000000..5894e734e --- /dev/null +++ b/drizzle/sqlite/0051_conscious_blur.sql @@ -0,0 +1,5 @@ +-- vault_profiles and vault_tokens move to the vault plugin's adopted tables +-- at activation, and ssh_data.vault_profile_id is copied into the vault +-- plugin's host settings by a boot migration after this runs. Dropping any +-- of them here would lose the data first. +SELECT 1; \ No newline at end of file diff --git a/drizzle/sqlite/meta/0051_snapshot.json b/drizzle/sqlite/meta/0051_snapshot.json new file mode 100644 index 000000000..bf49bce03 --- /dev/null +++ b/drizzle/sqlite/meta/0051_snapshot.json @@ -0,0 +1,4731 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "4eabeb88-7da2-487d-8539-060795941488", + "prevId": "a7a9b425-14aa-4f3a-bc25-8a3be6d25728", + "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": {} + }, + "dismissed_alerts": { + "name": "dismissed_alerts", + "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 + }, + "alert_id": { + "name": "alert_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "idx_dismissed_alerts_user_id": { + "name": "idx_dismissed_alerts_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "dismissed_alerts_user_id_users_id_fk": { + "name": "dismissed_alerts_user_id_users_id_fk", + "tableFrom": "dismissed_alerts", + "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_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 + }, + "autostart_password": { + "name": "autostart_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "autostart_key": { + "name": "autostart_key", + "type": "text(8192)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "autostart_key_password": { + "name": "autostart_key_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 + }, + "enable_terminal": { + "name": "enable_terminal", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "enable_session_logging": { + "name": "enable_session_logging", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "allow_session_sharing": { + "name": "allow_session_sharing", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "enable_command_history": { + "name": "enable_command_history", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "enable_tunnel": { + "name": "enable_tunnel", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "tunnel_connections": { + "name": "tunnel_connections", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "jump_hosts": { + "name": "jump_hosts", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enable_file_manager": { + "name": "enable_file_manager", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "scp_legacy": { + "name": "scp_legacy", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "enable_web_ui": { + "name": "enable_web_ui", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "enable_tmux_monitor": { + "name": "enable_tmux_monitor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "enable_terminal_toolbar": { + "name": "enable_terminal_toolbar", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "show_terminal_in_sidebar": { + "name": "show_terminal_in_sidebar", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "show_file_manager_in_sidebar": { + "name": "show_file_manager_in_sidebar", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "show_tunnel_in_sidebar": { + "name": "show_tunnel_in_sidebar", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "show_docker_in_sidebar": { + "name": "show_docker_in_sidebar", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "show_server_stats_in_sidebar": { + "name": "show_server_stats_in_sidebar", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "default_path": { + "name": "default_path", + "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 + }, + "web_ui_config": { + "name": "web_ui_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_config": { + "name": "terminal_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "quick_actions": { + "name": "quick_actions", + "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 + }, + "rdp_credential_id": { + "name": "rdp_credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_user": { + "name": "rdp_user", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_password": { + "name": "rdp_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_domain": { + "name": "rdp_domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vnc_credential_id": { + "name": "vnc_credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vnc_password": { + "name": "vnc_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vnc_user": { + "name": "vnc_user", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "telnet_user": { + "name": "telnet_user", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "telnet_password": { + "name": "telnet_password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "telnet_credential_id": { + "name": "telnet_credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rdp_auth_type": { + "name": "rdp_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vnc_auth_type": { + "name": "vnc_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "telnet_auth_type": { + "name": "telnet_auth_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "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 + }, + "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" + }, + "ssh_data_rdp_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_rdp_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "rdp_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_vnc_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_vnc_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "vnc_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ssh_data_telnet_credential_id_ssh_credentials_id_fk": { + "name": "ssh_data_telnet_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_data", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "telnet_credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notification_channels": { + "name": "notification_channels", + "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 + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "notification_channels_user_id_users_id_fk": { + "name": "notification_channels_user_id_users_id_fk", + "tableFrom": "notification_channels", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "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 + }, + "oidc_sub": { + "name": "oidc_sub", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "oidc_sid": { + "name": "oidc_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sso_provider_id": { + "name": "sso_provider_id", + "type": "integer", + "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 + }, + "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 + }, + "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 + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "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 + }, + "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" + }, + "ssh_folders_credential_id_ssh_credentials_id_fk": { + "name": "ssh_folders_credential_id_ssh_credentials_id_fk", + "tableFrom": "ssh_folders", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sync_tombstones": { + "name": "sync_tombstones", + "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 + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "sync_tombstones_user_id_users_id_fk": { + "name": "sync_tombstones_user_id_users_id_fk", + "tableFrom": "sync_tombstones", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "termix_identities": { + "name": "termix_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 + }, + "handle": { + "name": "handle", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "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": { + "termix_identities_user_id_unique": { + "name": "termix_identities_user_id_unique", + "columns": [ + "user_id" + ], + "isUnique": true + }, + "termix_identities_handle_unique": { + "name": "termix_identities_handle_unique", + "columns": [ + "handle" + ], + "isUnique": true + } + }, + "foreignKeys": { + "termix_identities_user_id_users_id_fk": { + "name": "termix_identities_user_id_users_id_fk", + "tableFrom": "termix_identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "termix_identity_ca": { + "name": "termix_identity_ca", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "identity_id": { + "name": "identity_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text(4096)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text(8192)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "validity_days": { + "name": "validity_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 90 + }, + "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": { + "termix_identity_ca_identity_id_unique": { + "name": "termix_identity_ca_identity_id_unique", + "columns": [ + "identity_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "termix_identity_ca_identity_id_termix_identities_id_fk": { + "name": "termix_identity_ca_identity_id_termix_identities_id_fk", + "tableFrom": "termix_identity_ca", + "tableTo": "termix_identities", + "columnsFrom": [ + "identity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_ca_user_id_users_id_fk": { + "name": "termix_identity_ca_user_id_users_id_fk", + "tableFrom": "termix_identity_ca", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "termix_identity_keys": { + "name": "termix_identity_keys", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "identity_id": { + "name": "identity_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text(8192)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "algorithm": { + "name": "algorithm", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'manual'" + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "termix_identity_keys_identity_id_termix_identities_id_fk": { + "name": "termix_identity_keys_identity_id_termix_identities_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "termix_identities", + "columnsFrom": [ + "identity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_keys_user_id_users_id_fk": { + "name": "termix_identity_keys_user_id_users_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "termix_identity_keys_credential_id_ssh_credentials_id_fk": { + "name": "termix_identity_keys_credential_id_ssh_credentials_id_fk", + "tableFrom": "termix_identity_keys", + "tableTo": "ssh_credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "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_autocomplete": { + "name": "command_autocomplete", + "type": "integer", + "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 + }, + "confirm_snippet_execution": { + "name": "confirm_snippet_execution", + "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_themes": { + "name": "custom_themes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "custom_keybindings": { + "name": "custom_keybindings", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_defaults": { + "name": "terminal_defaults", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_macros": { + "name": "terminal_macros", + "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 + }, + "is_oidc": { + "name": "is_oidc", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "oidc_identifier": { + "name": "oidc_identifier", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sso_provider_id": { + "name": "sso_provider_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "issuer_url": { + "name": "issuer_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "authorization_url": { + "name": "authorization_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_url": { + "name": "token_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "identifier_path": { + "name": "identifier_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name_path": { + "name": "name_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'openid email profile'" + }, + "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 ff7b5e4a5..31adfd3d3 100644 --- a/drizzle/sqlite/meta/_journal.json +++ b/drizzle/sqlite/meta/_journal.json @@ -358,6 +358,13 @@ "when": 1790295069807, "tag": "0050_clear_white_queen", "breakpoints": true + }, + { + "idx": 51, + "version": "6", + "when": 1790298803438, + "tag": "0051_conscious_blur", + "breakpoints": true } ] } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index db275afb1..a13bf503d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7060,6 +7060,10 @@ "resolved": "plugins/tunnels", "link": true }, + "node_modules/@termix-plugin/vault": { + "resolved": "plugins/vault", + "link": true + }, "node_modules/@termix-plugin/wake-on-lan": { "resolved": "plugins/wake-on-lan", "link": true @@ -18772,6 +18776,13 @@ "@termix/plugin-sdk": "*" } }, + "plugins/vault": { + "name": "@termix-plugin/vault", + "version": "1.0.0", + "devDependencies": { + "@termix/plugin-sdk": "*" + } + }, "plugins/wake-on-lan": { "name": "@termix-plugin/wake-on-lan", "version": "1.0.0", diff --git a/packages/plugin-sdk/ARCHITECTURE.md b/packages/plugin-sdk/ARCHITECTURE.md index dd369ee11..1dcf849db 100644 --- a/packages/plugin-sdk/ARCHITECTURE.md +++ b/packages/plugin-sdk/ARCHITECTURE.md @@ -185,7 +185,7 @@ artifacts in lockstep: the `schema.ts` declaration, the `index.ts` DDL, and the regenerated `schema.pg.ts`/`schema.mysql.ts` plus drizzle migrations. **Sync entities are registered, not hardcoded.** `ctx.sync.registerEntity` -adds an entity to remote sync; core registers its own ten the same way in +adds an entity to remote sync; core registers its own the same way in `database/routes/sync-entities.ts`. The wire names are unchanged, because existing tombstones and rows on the far side match on those strings. The Electron client asks the server for the ordered list and falls back to its @@ -1008,12 +1008,10 @@ auto-session and trusted devices, plus the base SSH auth types password, key, stored credential, agent and none. Every other login method, second factor and SSH auth method is a plugin through `ctx.auth`. -In 2.9.0, Vault still lives in core. It registers through the same -interface from `src/backend/auth/legacy-providers.ts` with -`pluginId: "core"`. Its Phase C step moves its block out of that file; D1 -deletes it. **C4** moved Step-CA out and deleted -`src/ui/auth/legacy-auth-ui.tsx`, which had no other editor left. -Nothing else in core branches on those type names. +**C5** moved Vault, the last SSH auth method core still registered, into +its plugin and deleted `src/backend/auth/legacy-providers.ts`. **C4** had +already deleted `src/ui/auth/legacy-auth-ui.tsx`. Core registers only the +base types and branches on no plugin type name. **C1** moved TOTP and passkeys out: @@ -1137,7 +1135,7 @@ Nothing else in core branches on those type names. `requires_browser_sign_in` and continue on `connect-browser-sign-in`. - `@termix/plugin-sdk/ssh-certs` (`applyCertificateAuth`) replaces core's `opkssh-cert-auth.ts`: opkssh, step-ca, core key auth with a CA - certificate, and the Vault provider still in core use it. + certificate, and vault use it. **C4** moved Step-CA out: @@ -1159,6 +1157,39 @@ Nothing else in core branches on those type names. - The sign-in dialog is a `terminal.overlay` contribution speaking `stepca_*`. +**C5** moved HashiCorp Vault out: + +- `vault` registers the `vault` SSH auth provider. It adopts + `vault_profiles` and `vault_tokens` as `p_vault_profiles` and + `p_vault_tokens`. Profiles keep their ids and sync ids; cached + certificates are sealed with `ctx.secrets.seal`, and the ones 2.8 wrote + with the user's data key do not unseal, so they are dropped and cost one + sign-in. A fresh install has no foreign key from a token to its profile + (the SDK has no builder for one to a plugin's own table), so deleting a + profile deletes its tokens in code. +- A host's profile is the `profileId` host setting (hidden, edited in the + plugin's `registerSshAuthEditor` editor, which also holds the profile + manager where the host editor always had it). + `vault-settings-migration.ts` copies `ssh_data.vault_profile_id` into it; + the column is out of `schema.ts` and its drizzle drop is a no-op. +- Vault is reached through `ctx.fetch`, with the profile's own Vault host + passed as `allowPrivateHosts`, since Vault servers are usually internal. +- Profile routes are `/plugin-api/vault/profiles`, gated on `vault.use`; + sharing a profile needs `vault.share` (admins by default), which replaces + 2.8's admin check. Only the owner edits or deletes one. +- The sync entity keeps its wire name `vaultProfiles` and order 20. Hosts no + longer reference it, so core's host entity lost its `vaultProfileId` + reference and `sync-references.ts` has no Vault case. +- The callback is `/plugin-api/vault/oidc/callback` (public). The boot + migration decides `legacyCallback` once: on when the 2.8 database used + Vault, so Termix keeps sending `/vault/oidc/callback`, the URI Vault + roles allow, which core answers with a 307 to the plugin + (`registerVaultCompatRoutes` in `host-compat-routes.ts`; nginx proxies + only that path now). +- The sign-in dialog is a `terminal.overlay` contribution. It keeps the 2.8 + message names (`vault_auth_url`, `vault_completed`, `vault_error`) for + Termix-Mobile. + #### One SSH connect pipeline Every SSH connection, core's and plugins', goes through @@ -2298,8 +2329,7 @@ upgrade on all three engines, and the checks in step 15 are green. (`ctx.disposables.add`). Nothing at module scope. `deactivate` is usually empty. 4. **When the SDK does not have it, add it to the SDK.** Never work around a - gap with a core import, and never add to - `src/backend/auth/legacy-providers.ts`. A new ctx member is: + gap with a core import. A new ctx member is: 1. the type and a doc comment in `packages/plugin-sdk/src/backend.ts`; 2. the implementation in `src/backend/plugins/ctx.ts`, wrapped in `guarded()` when it is privileged, so it checks the capability and writes an audit @@ -2540,7 +2570,7 @@ Then, with the app running: The bundled plugins predate the SDK, apart from workspaces (A9), snippets (B2), remote-desktop (B14), docker (B15), host-metrics (B16), automations (B17), ai (B18), homepage (B19), totp and webauthn (C1), sso and ldap (C2), -opkssh and warpgate (C3), step-ca (C4), +opkssh and warpgate (C3), step-ca (C4), vault (C5), which import nothing from core. The others still reach core by relative path (`../../../../src/backend/...`), which an esbuild plugin, `packages/plugin-sdk/cli/lib/legacy-core-imports.mjs`, keeps out of the bundle diff --git a/packages/plugin-sdk/FINISH-LIST.md b/packages/plugin-sdk/FINISH-LIST.md index 66994b3ea..3b666682d 100644 --- a/packages/plugin-sdk/FINISH-LIST.md +++ b/packages/plugin-sdk/FINISH-LIST.md @@ -529,6 +529,31 @@ build`'s esbuild step has no static-asset-copy pipeline the way core's Vite rejects (it then downloads the pinned version instead). Pass `OPKSSH_VERSION` and the matching `OPKSSH_SHA256` as `ENV` in the final stage. Owner: D0. +- **C5 (vault):** the Termix docs still describe Vault profiles with the + `/vault/oidc/callback` redirect URI. They need the plugin settings page, + the new redirect URI (`/plugin-api/vault/oidc/callback`) and the + `legacyCallback` setting. Owner: D0 (docs repo). +- **C5 (vault):** a host's Vault profile is the plugin's `profileId` host + setting now, and host-scope plugin settings do not travel over remote sync + (the host entity used to carry `vaultProfileSyncId`). Profiles still sync + as `vaultProfiles`; the host on the other side has to pick its profile + again. Needs a generic way to sync host-scope plugin settings, the same gap + the B7 export/import line describes. Owner: D1. +- **C5 (vault), for D2:** `vaultProfileId` was an owner-private auth field a + shared-host recipient with edit access could not change. As a host setting + it is written through `/plugins/vault/settings/host/:hostId`, which only + checks edit access, so such a recipient can now point the host at another + profile. Same question as the B14 line on per-field sharing levels. + Owner: D2. +- **C5 (vault):** `ssh_data.vault_profile_id` stays in the database, + unused, after `vault-settings-migration.ts` copies it (kept so a downgrade + works, and the pg/mysql foreign key on it now points at + `p_vault_profiles`). Drop it in 3.0.0. Owner: 3.0.0. +- **Pre-existing, found in C5:** `npm run test -w plugins/ssh-terminal` + fails "ssh-terminal activate > serves the terminal socket": it expects + `mock.wsRoutes` to equal `[{ path, raw }]`, but `FakeWsRoute` now also + records `handler` and `options`. Use `toMatchObject` or + `expect.objectContaining`. Owner: D0. ## Manual checks after 2.9.0 @@ -624,3 +649,11 @@ build`'s esbuild step has no static-asset-copy pipeline the way core's Vite and the host says it needs it. A Warpgate host's toggle is on in the host editor's Plugins group after the upgrade, and the terminal, file manager and docker each show the sign-in dialog and connect after it. +- Vault: on a 2.8 database every Vault profile is there and each Vault host + still has its profile selected in the host editor; the admin page shows the + old redirect URI; a terminal connect opens Vault's sign-in, signs a + certificate and connects, and a second connect skips the browser until the + certificate expires; create, edit, share and delete a profile from the host + editor (sharing only offered with "Share Vault profiles"); switch to the new + redirect URI after allowing it in the Vault role; disable the vault plugin + and a Vault host says it needs it, then enable it again. diff --git a/plugins/file-manager/src/backend/index.ts b/plugins/file-manager/src/backend/index.ts index cb24c760c..5ef8bebfb 100644 --- a/plugins/file-manager/src/backend/index.ts +++ b/plugins/file-manager/src/backend/index.ts @@ -584,7 +584,6 @@ export async function activate(ctx: PluginContext) { let resolvedSocks5Username = socks5Username; let resolvedSocks5Password = socks5Password; let resolvedSocks5ProxyChain = socks5ProxyChain; - let resolvedVaultProfileId: number | null = null; const resolveFrom = async () => { const resolvedHost = hostSyncId @@ -607,8 +606,6 @@ export async function activate(ctx: PluginContext) { resolvedTerminalConfig = resolvedHost.terminalConfig as unknown as Record | undefined; resolvedScpLegacy = resolvedHost.scpLegacy ?? false; - resolvedVaultProfileId = - (resolvedHost.vaultProfile as { id?: number } | undefined)?.id ?? null; if (resolvedHost.useSocks5) { resolvedUseSocks5 = resolvedHost.useSocks5; resolvedSocks5Host = resolvedHost.socks5Host; @@ -717,9 +714,6 @@ export async function activate(ctx: PluginContext) { keyPassword: resolvedCredentials.keyPassword, certPublicKey: resolvedCredentials.certPublicKey, terminalConfig: resolvedTerminalConfig ?? null, - vaultProfile: resolvedVaultProfileId - ? { id: resolvedVaultProfileId } - : null, jumpHosts: resolvedJumpHosts, useSocks5: resolvedUseSocks5, socks5Host: resolvedSocks5Host, diff --git a/plugins/ssh-terminal/locales/en.json b/plugins/ssh-terminal/locales/en.json index 64440cc63..3f107abb1 100644 --- a/plugins/ssh-terminal/locales/en.json +++ b/plugins/ssh-terminal/locales/en.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} window", "tmuxWindows": "Windows", "unknownError": "Unknown error occurred", - "vaultAuthDescription": "A window has opened to sign in to HashiCorp Vault. Complete the sign-in there; this connection will continue automatically.", - "vaultAuthFailed": "Vault authentication failed. Please try again.", - "vaultAuthTitle": "Vault sign-in required", - "vaultReopen": "Reopen sign-in window", "websocketAbnormalClose": "Connection closed unexpectedly. This may be due to a reverse proxy or SSL configuration issue. Please check server logs.", "websocketError": "WebSocket connection error" }, diff --git a/plugins/ssh-terminal/locales/translated/af_ZA.json b/plugins/ssh-terminal/locales/translated/af_ZA.json index 74344a3d4..7e4b1a725 100644 --- a/plugins/ssh-terminal/locales/translated/af_ZA.json +++ b/plugins/ssh-terminal/locales/translated/af_ZA.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} venster", "tmuxWindows": "Vensters", "unknownError": "Onbekende fout het voorgekom", - "vaultAuthDescription": "'n Venster het oopgemaak om by HashiCorp Vault aan te meld. Voltooi die aanmelding daar; hierdie verbinding sal outomaties voortgaan.", - "vaultAuthFailed": "Kluisverifikasie het misluk. Probeer asseblief weer.", - "vaultAuthTitle": "Kluisaanmelding vereis", - "vaultReopen": "Maak aanmeldvenster weer oop", "websocketAbnormalClose": "Verbinding het onverwags gesluit. Dit kan wees as gevolg van 'n omgekeerde proxy- of SSL-konfigurasieprobleem. Gaan asseblief bedienerlogboeke na.", "websocketError": "WebSocket-verbindingsfout" }, diff --git a/plugins/ssh-terminal/locales/translated/ar_SA.json b/plugins/ssh-terminal/locales/translated/ar_SA.json index 472366f8d..bd8d0f893 100644 --- a/plugins/ssh-terminal/locales/translated/ar_SA.json +++ b/plugins/ssh-terminal/locales/translated/ar_SA.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} نافذة", "tmuxWindows": "النوافذ", "unknownError": "حدث خطأ غير معروف", - "vaultAuthDescription": "تم فتح نافذة لتسجيل الدخول إلى HashiCorp Vault. أكمل تسجيل الدخول هناك؛ سيستمر هذا الاتصال تلقائيًا.", - "vaultAuthFailed": "فشلت مصادقة Vault. يرجى المحاولة مرة أخرى.", - "vaultAuthTitle": "مطلوب تسجيل الدخول إلى Vault", - "vaultReopen": "أعد فتح نافذة تسجيل الدخول", "websocketAbnormalClose": "أُغلق الاتصال بشكل غير متوقع. قد يكون السبب مشكلة في الوكيل العكسي أو إعدادات SSL. يرجى مراجعة سجلات الخادم.", "websocketError": "خطأ في اتصال WebSocket" }, diff --git a/plugins/ssh-terminal/locales/translated/bg_BG.json b/plugins/ssh-terminal/locales/translated/bg_BG.json index d56a7dfc0..d25d8d430 100644 --- a/plugins/ssh-terminal/locales/translated/bg_BG.json +++ b/plugins/ssh-terminal/locales/translated/bg_BG.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} прозорец", "tmuxWindows": "Прозорци", "unknownError": "Възникна неизвестна грешка", - "vaultAuthDescription": "Отвори се прозорец за влизане в HashiCorp Vault. Завършете влизането там; тази връзка ще продължи автоматично.", - "vaultAuthFailed": "Удостоверяването на трезора не бе успешно. Моля, опитайте отново.", - "vaultAuthTitle": "Изисква се влизане в трезора", - "vaultReopen": "Отваряне отново на прозореца за влизане", "websocketAbnormalClose": "Връзката се прекъсна неочаквано. Това може да се дължи на проблем с обратен прокси или SSL конфигурация. Моля, проверете лог файловете на сървъра.", "websocketError": "Грешка при свързване с WebSocket" }, diff --git a/plugins/ssh-terminal/locales/translated/bn_BD.json b/plugins/ssh-terminal/locales/translated/bn_BD.json index 84b03e9db..62b37a090 100644 --- a/plugins/ssh-terminal/locales/translated/bn_BD.json +++ b/plugins/ssh-terminal/locales/translated/bn_BD.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} জানালা", "tmuxWindows": "উইন্ডোজ", "unknownError": "অজানা ত্রুটি ঘটেছে", - "vaultAuthDescription": "হ্যাশিকর্প ভল্টে সাইন ইন করার জন্য একটি উইন্ডো খুলেছে। সেখানে সাইন-ইন সম্পন্ন করুন; এই সংযোগটি স্বয়ংক্রিয়ভাবে চলতে থাকবে।", - "vaultAuthFailed": "ভল্ট প্রমাণীকরণ ব্যর্থ হয়েছে। অনুগ্রহ করে আবার চেষ্টা করুন।", - "vaultAuthTitle": "ভল্টে সাইন-ইন করা আবশ্যক", - "vaultReopen": "সাইন-ইন উইন্ডো পুনরায় খুলুন", "websocketAbnormalClose": "সংযোগটি অপ্রত্যাশিতভাবে বন্ধ হয়ে গেছে। এটি রিভার্স প্রক্সি বা SSL কনফিগারেশন সংক্রান্ত সমস্যার কারণে হতে পারে। অনুগ্রহ করে সার্ভার লগ পরীক্ষা করুন।", "websocketError": "ওয়েবসকেট সংযোগ ত্রুটি" }, diff --git a/plugins/ssh-terminal/locales/translated/ca_ES.json b/plugins/ssh-terminal/locales/translated/ca_ES.json index 9762f9887..173b02256 100644 --- a/plugins/ssh-terminal/locales/translated/ca_ES.json +++ b/plugins/ssh-terminal/locales/translated/ca_ES.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "finestra {{count}}", "tmuxWindows": "Finestres", "unknownError": "S'ha produït un error desconegut", - "vaultAuthDescription": "S'ha obert una finestra per iniciar la sessió a HashiCorp Vault. Completeu l'inici de sessió allà; aquesta connexió continuarà automàticament.", - "vaultAuthFailed": "L'autenticació de la caixa forta ha fallat. Torna-ho a provar.", - "vaultAuthTitle": "Cal iniciar sessió a la caixa forta", - "vaultReopen": "Torna a obrir la finestra d'inici de sessió", "websocketAbnormalClose": "La connexió s'ha tancat inesperadament. Això pot ser degut a un problema de configuració del proxy invers o de l'SSL. Si us plau, reviseu els registres del servidor.", "websocketError": "Error de connexió de WebSocket" }, diff --git a/plugins/ssh-terminal/locales/translated/cs_CZ.json b/plugins/ssh-terminal/locales/translated/cs_CZ.json index 2cfe30e9f..4f4748fd7 100644 --- a/plugins/ssh-terminal/locales/translated/cs_CZ.json +++ b/plugins/ssh-terminal/locales/translated/cs_CZ.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} okno", "tmuxWindows": "Okna", "unknownError": "Došlo k neznámé chybě", - "vaultAuthDescription": "Otevřelo se okno pro přihlášení do HashiCorp Vault. Dokončete přihlášení tam; toto připojení bude automaticky pokračovat.", - "vaultAuthFailed": "Ověření v trezoru se nezdařilo. Zkuste to prosím znovu.", - "vaultAuthTitle": "Vyžaduje se přihlášení do trezoru", - "vaultReopen": "Znovu otevřít přihlašovací okno", "websocketAbnormalClose": "Spojení se neočekávaně ukončilo. Může to být způsobeno problémem s reverzní proxy nebo konfigurací SSL. Zkontrolujte prosím protokoly serveru.", "websocketError": "Chyba připojení k WebSocketu" }, diff --git a/plugins/ssh-terminal/locales/translated/da_DK.json b/plugins/ssh-terminal/locales/translated/da_DK.json index 5b274da4e..18b5122be 100644 --- a/plugins/ssh-terminal/locales/translated/da_DK.json +++ b/plugins/ssh-terminal/locales/translated/da_DK.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} vindue", "tmuxWindows": "Vinduer", "unknownError": "Der opstod en ukendt fejl", - "vaultAuthDescription": "Et vindue er åbnet, hvor du kan logge ind på HashiCorp Vault. Fuldfør loginprocessen der; forbindelsen fortsætter automatisk.", - "vaultAuthFailed": "Godkendelse af arkiv mislykkedes. Prøv igen.", - "vaultAuthTitle": "Login til Vault kræves", - "vaultReopen": "Genåbn loginvinduet", "websocketAbnormalClose": "Forbindelsen blev lukket uventet. Dette kan skyldes et problem med reverse proxy eller SSL-konfigurationen. Tjek venligst serverlogfilerne.", "websocketError": "WebSocket-forbindelsesfejl" }, diff --git a/plugins/ssh-terminal/locales/translated/de_DE.json b/plugins/ssh-terminal/locales/translated/de_DE.json index 8c3fce5e3..636207d22 100644 --- a/plugins/ssh-terminal/locales/translated/de_DE.json +++ b/plugins/ssh-terminal/locales/translated/de_DE.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} Fenster", "tmuxWindows": "Fenster", "unknownError": "Unbekannter Fehler aufgetreten", - "vaultAuthDescription": "Ein Fenster zur Anmeldung bei HashiCorp Vault wurde geöffnet. Schließen Sie die Anmeldung dort ab; diese Verbindung wird automatisch fortgesetzt.", - "vaultAuthFailed": "Vault-Authentifizierung fehlgeschlagen. Bitte versuchen Sie es erneut.", - "vaultAuthTitle": "Vault-Anmeldung erforderlich", - "vaultReopen": "Anmeldefenster erneut öffnen", "websocketAbnormalClose": "Verbindung unerwartet geschlossen. Dies kann auf ein Problem mit dem Reverse-Proxy oder der SSL-Konfiguration zurückzuführen sein. Bitte überprüfen Sie die Server-Logs.", "websocketError": "WebSocket-Verbindungsfehler" }, diff --git a/plugins/ssh-terminal/locales/translated/el_GR.json b/plugins/ssh-terminal/locales/translated/el_GR.json index e6483b28d..2e31786e4 100644 --- a/plugins/ssh-terminal/locales/translated/el_GR.json +++ b/plugins/ssh-terminal/locales/translated/el_GR.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "παράθυρο {{count}}", "tmuxWindows": "Παράθυρα", "unknownError": "Παρουσιάστηκε άγνωστο σφάλμα", - "vaultAuthDescription": "Έχει ανοίξει ένα παράθυρο για να συνδεθείτε στο HashiCorp Vault. Ολοκληρώστε τη σύνδεση εκεί. Αυτή η σύνδεση θα συνεχιστεί αυτόματα.", - "vaultAuthFailed": "Ο έλεγχος ταυτότητας του vault απέτυχε. Δοκιμάστε ξανά.", - "vaultAuthTitle": "Απαιτείται σύνδεση στο Vault", - "vaultReopen": "Επανάνοιγμα παραθύρου σύνδεσης", "websocketAbnormalClose": "Η σύνδεση έκλεισε απροσδόκητα. Αυτό μπορεί να οφείλεται σε πρόβλημα αντίστροφης μεσολάβησης ή ρύθμισης παραμέτρων SSL. Ελέγξτε τα αρχεία καταγραφής διακομιστή.", "websocketError": "Σφάλμα σύνδεσης WebSocket" }, diff --git a/plugins/ssh-terminal/locales/translated/es_ES.json b/plugins/ssh-terminal/locales/translated/es_ES.json index 35d22f03b..7eed401bd 100644 --- a/plugins/ssh-terminal/locales/translated/es_ES.json +++ b/plugins/ssh-terminal/locales/translated/es_ES.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} ventana", "tmuxWindows": "Ventanas", "unknownError": "Se ha producido un error desconocido.", - "vaultAuthDescription": "Se ha abierto una ventana para iniciar sesión en HashiCorp Vault. Completa el inicio de sesión allí; esta conexión continuará automáticamente.", - "vaultAuthFailed": "La autenticación de Vault falló. Inténtalo de nuevo.", - "vaultAuthTitle": "Inicio de sesión en Vault requerido", - "vaultReopen": "Reabrir ventana de inicio de sesión", "websocketAbnormalClose": "La conexión se cerró de forma inesperada. Esto puede deberse a un problema con el proxy inverso o la configuración SSL. Revisa los registros del servidor.", "websocketError": "Error de conexión WebSocket" }, diff --git a/plugins/ssh-terminal/locales/translated/fi_FI.json b/plugins/ssh-terminal/locales/translated/fi_FI.json index e0c76fd08..6a389e86d 100644 --- a/plugins/ssh-terminal/locales/translated/fi_FI.json +++ b/plugins/ssh-terminal/locales/translated/fi_FI.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} ikkuna", "tmuxWindows": "Ikkunat", "unknownError": "Tuntematon virhe tapahtui", - "vaultAuthDescription": "Ikkuna HashiCorp-holviin kirjautumista varten on avautunut. Kirjaudu sisään siinä. Yhteys jatkuu automaattisesti.", - "vaultAuthFailed": "Holvin todennus epäonnistui. Yritä uudelleen.", - "vaultAuthTitle": "Holviin kirjautuminen vaaditaan", - "vaultReopen": "Avaa kirjautumisikkuna uudelleen", "websocketAbnormalClose": "Yhteys katkesi odottamatta. Tämä voi johtua käänteisestä välityspalvelimesta tai SSL-määritysongelmasta. Tarkista palvelimen lokit.", "websocketError": "WebSocket-yhteysvirhe" }, diff --git a/plugins/ssh-terminal/locales/translated/fr_FR.json b/plugins/ssh-terminal/locales/translated/fr_FR.json index ad56f84ed..a7f77e9bd 100644 --- a/plugins/ssh-terminal/locales/translated/fr_FR.json +++ b/plugins/ssh-terminal/locales/translated/fr_FR.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} fenêtre(s)", "tmuxWindows": "Fenêtres", "unknownError": "Une erreur inconnue s'est produite", - "vaultAuthDescription": "Une fenêtre s'est ouverte pour vous connecter à HashiCorp Vault. Terminez la connexion dans cette fenêtre ; cette connexion continuera automatiquement.", - "vaultAuthFailed": "Échec de l'authentification Vault. Veuillez réessayer.", - "vaultAuthTitle": "Connexion à Vault requise", - "vaultReopen": "Rouvrir la fenêtre de connexion", "websocketAbnormalClose": "Connexion fermée de manière inattendue. Cela peut être dû à un problème de proxy inverse ou de configuration SSL. Veuillez vérifier les journaux du serveur.", "websocketError": "Erreur de connexion WebSocket" }, diff --git a/plugins/ssh-terminal/locales/translated/he_IL.json b/plugins/ssh-terminal/locales/translated/he_IL.json index fdbe0851b..6b4f7802f 100644 --- a/plugins/ssh-terminal/locales/translated/he_IL.json +++ b/plugins/ssh-terminal/locales/translated/he_IL.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "חלון {{count}}", "tmuxWindows": "חלונות", "unknownError": "אירעה שגיאה לא ידועה", - "vaultAuthDescription": "נפתח חלון לכניסה לכספת HashiCorp. השלם את תהליך הכניסה שם; חיבור זה ימשיך אוטומטית.", - "vaultAuthFailed": "אימות הכספת נכשל. אנא נסה שוב.", - "vaultAuthTitle": "נדרשת כניסה לכספת", - "vaultReopen": "פתח מחדש את חלון הכניסה", "websocketAbnormalClose": "החיבור נסגר באופן בלתי צפוי. ייתכן שהדבר נובע מבעיית פרוקסי הפוך או מבעיית תצורת SSL. אנא בדוק את יומני השרת.", "websocketError": "שגיאת חיבור WebSocket" }, diff --git a/plugins/ssh-terminal/locales/translated/hi_IN.json b/plugins/ssh-terminal/locales/translated/hi_IN.json index dcb259247..9c33c7029 100644 --- a/plugins/ssh-terminal/locales/translated/hi_IN.json +++ b/plugins/ssh-terminal/locales/translated/hi_IN.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} विंडो", "tmuxWindows": "विंडो", "unknownError": "अज्ञात त्रुटि हुई", - "vaultAuthDescription": "HashiCorp Vault में साइन इन करने के लिए एक विंडो खुल गई है। वहाँ साइन-इन पूरा करें; यह कनेक्शन स्वचालित रूप से जारी रहेगा।", - "vaultAuthFailed": "Vault प्रमाणीकरण विफल। कृपया पुनः प्रयास करें।", - "vaultAuthTitle": "Vault साइन-इन आवश्यक है", - "vaultReopen": "साइन-इन विंडो फिर से खोलें", "websocketAbnormalClose": "कनेक्शन अप्रत्याशित रूप से बंद हो गया। यह रिवर्स प्रॉक्सी या SSL कॉन्फ़िगरेशन समस्या के कारण हो सकता है। कृपया सर्वर लॉग जाँचें।", "websocketError": "WebSocket कनेक्शन त्रुटि" }, diff --git a/plugins/ssh-terminal/locales/translated/hu_HU.json b/plugins/ssh-terminal/locales/translated/hu_HU.json index e6cc51646..a722d6059 100644 --- a/plugins/ssh-terminal/locales/translated/hu_HU.json +++ b/plugins/ssh-terminal/locales/translated/hu_HU.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} ablak", "tmuxWindows": "Ablakok", "unknownError": "Ismeretlen hiba történt", - "vaultAuthDescription": "Megnyílt egy ablak a HashiCorp Vaultba való bejelentkezéshez. Fejezze be a bejelentkezést ott; a kapcsolat automatikusan folytatódik.", - "vaultAuthFailed": "A trezor hitelesítése sikertelen. Próbálja újra.", - "vaultAuthTitle": "Bejelentkezés szükséges a széfbe", - "vaultReopen": "Bejelentkezési ablak újranyitása", "websocketAbnormalClose": "A kapcsolat váratlanul megszakadt. Ennek oka lehet fordított proxy vagy SSL konfigurációs probléma. Kérjük, ellenőrizze a szervernaplókat.", "websocketError": "WebSocket csatlakozási hiba" }, diff --git a/plugins/ssh-terminal/locales/translated/id_ID.json b/plugins/ssh-terminal/locales/translated/id_ID.json index 4552ffcb3..6527f9d0e 100644 --- a/plugins/ssh-terminal/locales/translated/id_ID.json +++ b/plugins/ssh-terminal/locales/translated/id_ID.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} jendela", "tmuxWindows": "Windows", "unknownError": "Terjadi kesalahan yang tidak diketahui.", - "vaultAuthDescription": "Jendela telah terbuka untuk masuk ke HashiCorp Vault. Selesaikan proses masuk di sana; koneksi ini akan berlanjut secara otomatis.", - "vaultAuthFailed": "Autentikasi Vault gagal. Silakan coba lagi.", - "vaultAuthTitle": "Login ke Vault diperlukan.", - "vaultReopen": "Buka kembali jendela masuk", "websocketAbnormalClose": "Koneksi terputus secara tidak terduga. Ini mungkin disebabkan oleh masalah konfigurasi reverse proxy atau SSL. Silakan periksa log server.", "websocketError": "Kesalahan koneksi WebSocket" }, diff --git a/plugins/ssh-terminal/locales/translated/it_IT.json b/plugins/ssh-terminal/locales/translated/it_IT.json index fe7972d2a..7dd04d443 100644 --- a/plugins/ssh-terminal/locales/translated/it_IT.json +++ b/plugins/ssh-terminal/locales/translated/it_IT.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} finestra", "tmuxWindows": "Finestre", "unknownError": "Si è verificato un errore sconosciuto", - "vaultAuthDescription": "Una finestra si è aperta per accedere a HashiCorp Vault. Completa l'accesso lì; la connessione continuerà automaticamente.", - "vaultAuthFailed": "Autenticazione Vault fallita. Riprova.", - "vaultAuthTitle": "Accesso a Vault richiesto", - "vaultReopen": "Riapri la finestra di accesso", "websocketAbnormalClose": "La connessione si è chiusa in modo imprevisto. Potrebbe trattarsi di un problema di reverse proxy o configurazione SSL. Controlla i log del server.", "websocketError": "Errore di connessione WebSocket" }, diff --git a/plugins/ssh-terminal/locales/translated/ja_JP.json b/plugins/ssh-terminal/locales/translated/ja_JP.json index 84e7d9e30..c601e8995 100644 --- a/plugins/ssh-terminal/locales/translated/ja_JP.json +++ b/plugins/ssh-terminal/locales/translated/ja_JP.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} ウィンドウ", "tmuxWindows": "ウィンドウ", "unknownError": "不明なエラーが発生しました", - "vaultAuthDescription": "HashiCorp Vaultへのサインイン用のウィンドウが開きました。そこでサインインを完了すると、この接続は自動的に続行されます。", - "vaultAuthFailed": "Vault 認証に失敗しました。もう一度お試しください。", - "vaultAuthTitle": "Vault サインインが必要です", - "vaultReopen": "サインインウィンドウを再表示", "websocketAbnormalClose": "接続が予期せず切断されました。リバースプロキシまたはSSLの設定に問題がある可能性があります。サーバーログを確認してください。", "websocketError": "WebSocket接続エラー" }, diff --git a/plugins/ssh-terminal/locales/translated/ko_KR.json b/plugins/ssh-terminal/locales/translated/ko_KR.json index 9dd804185..3350ea7f7 100644 --- a/plugins/ssh-terminal/locales/translated/ko_KR.json +++ b/plugins/ssh-terminal/locales/translated/ko_KR.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}}개 윈도우", "tmuxWindows": "윈도우", "unknownError": "알 수 없는 오류가 발생했습니다.", - "vaultAuthDescription": "HashiCorp Vault 로그인을 위한 창이 열렸습니다. 해당 창에서 로그인을 완료하면 연결이 자동으로 계속됩니다.", - "vaultAuthFailed": "Vault 인증에 실패했습니다. 다시 시도하세요.", - "vaultAuthTitle": "Vault 로그인 필요", - "vaultReopen": "로그인 창 다시 열기", "websocketAbnormalClose": "연결이 예기치 않게 종료되었습니다. 리버스 프록시 또는 SSL 구성 문제일 수 있습니다. 서버 로그를 확인하세요.", "websocketError": "WebSocket 연결 오류" }, diff --git a/plugins/ssh-terminal/locales/translated/nl_NL.json b/plugins/ssh-terminal/locales/translated/nl_NL.json index b91b759c9..57f619813 100644 --- a/plugins/ssh-terminal/locales/translated/nl_NL.json +++ b/plugins/ssh-terminal/locales/translated/nl_NL.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} venster", "tmuxWindows": "Windows", "unknownError": "Er is een onbekende fout opgetreden.", - "vaultAuthDescription": "Er is een venster geopend om in te loggen bij HashiCorp Vault. Voltooi de aanmelding daar; deze verbinding wordt automatisch tot stand gebracht.", - "vaultAuthFailed": "Authenticatie voor de kluis is mislukt. Probeer het opnieuw.", - "vaultAuthTitle": "Aanmelden bij Vault is vereist.", - "vaultReopen": "Heropen het aanmeldvenster", "websocketAbnormalClose": "De verbinding is onverwacht verbroken. Dit kan te wijten zijn aan een probleem met de reverse proxy of de SSL-configuratie. Controleer de serverlogboeken.", "websocketError": "WebSocket-verbindingsfout" }, diff --git a/plugins/ssh-terminal/locales/translated/no_NO.json b/plugins/ssh-terminal/locales/translated/no_NO.json index 2be596ebf..6459b30c5 100644 --- a/plugins/ssh-terminal/locales/translated/no_NO.json +++ b/plugins/ssh-terminal/locales/translated/no_NO.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} vindu", "tmuxWindows": "Vinduer", "unknownError": "Ukjent feil oppstod", - "vaultAuthDescription": "Et vindu har åpnet seg for å logge på HashiCorp Vault. Fullfør påloggingen der; denne tilkoblingen vil fortsette automatisk.", - "vaultAuthFailed": "Hvelv-autentisering mislyktes. Prøv på nytt.", - "vaultAuthTitle": "Pålogging til arkivet kreves", - "vaultReopen": "Åpne påloggingsvinduet på nytt", "websocketAbnormalClose": "Tilkoblingen ble avsluttet uventet. Dette kan skyldes et problem med omvendt proxy eller SSL-konfigurasjon. Sjekk serverloggene.", "websocketError": "WebSocket-tilkoblingsfeil" }, diff --git a/plugins/ssh-terminal/locales/translated/pl_PL.json b/plugins/ssh-terminal/locales/translated/pl_PL.json index 9faf1fadf..eba32ee05 100644 --- a/plugins/ssh-terminal/locales/translated/pl_PL.json +++ b/plugins/ssh-terminal/locales/translated/pl_PL.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} okno", "tmuxWindows": "Okna", "unknownError": "Wystąpił nieznany błąd", - "vaultAuthDescription": "Otworzyło się okno logowania do HashiCorp Vault. Dokończ logowanie, a połączenie zostanie nawiązane automatycznie.", - "vaultAuthFailed": "Uwierzytelnienie w Vault nie powiodło się. Spróbuj ponownie.", - "vaultAuthTitle": "Wymagane logowanie do Vault", - "vaultReopen": "Otwórz ponownie okno logowania", "websocketAbnormalClose": "Połączenie zostało nieoczekiwanie zamknięte. Przyczyną może być reverse proxy lub problem z konfiguracją SSL. Sprawdź logi serwera.", "websocketError": "Błąd połączenia WebSocket" }, diff --git a/plugins/ssh-terminal/locales/translated/pt_BR.json b/plugins/ssh-terminal/locales/translated/pt_BR.json index 914607d3e..bb3d9529d 100644 --- a/plugins/ssh-terminal/locales/translated/pt_BR.json +++ b/plugins/ssh-terminal/locales/translated/pt_BR.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} janela", "tmuxWindows": "Windows", "unknownError": "Ocorreu um erro desconhecido.", - "vaultAuthDescription": "Uma janela foi aberta para fazer login no HashiCorp Vault. Conclua o login; esta conexão será estabelecida automaticamente.", - "vaultAuthFailed": "A autenticação do cofre falhou. Tente novamente.", - "vaultAuthTitle": "É necessário fazer login no cofre.", - "vaultReopen": "Reabrir janela de login", "websocketAbnormalClose": "A conexão foi encerrada inesperadamente. Isso pode ter ocorrido devido a um problema de configuração de proxy reverso ou SSL. Verifique os registros do servidor.", "websocketError": "Erro de conexão WebSocket" }, diff --git a/plugins/ssh-terminal/locales/translated/pt_PT.json b/plugins/ssh-terminal/locales/translated/pt_PT.json index abee07b33..6241aa50f 100644 --- a/plugins/ssh-terminal/locales/translated/pt_PT.json +++ b/plugins/ssh-terminal/locales/translated/pt_PT.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} janela", "tmuxWindows": "Janelas", "unknownError": "Ocorreu um erro desconhecido.", - "vaultAuthDescription": "Foi aberta uma janela para iniciar sessão no HashiCorp Vault. Conclua o início de sessão aí; esta ligação continuará automaticamente.", - "vaultAuthFailed": "A autenticação no Vault falhou. Tente novamente.", - "vaultAuthTitle": "Início de sessão no Vault necessário", - "vaultReopen": "Reabrir janela de início de sessão", "websocketAbnormalClose": "A ligação foi fechada inesperadamente. Isto pode dever-se a um problema de configuração do proxy reverso ou SSL. Verifique os registos do servidor.", "websocketError": "Erro de ligação WebSocket" }, diff --git a/plugins/ssh-terminal/locales/translated/ro_RO.json b/plugins/ssh-terminal/locales/translated/ro_RO.json index 3fd28d6c4..8240a1214 100644 --- a/plugins/ssh-terminal/locales/translated/ro_RO.json +++ b/plugins/ssh-terminal/locales/translated/ro_RO.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "fereastră {{count}}", "tmuxWindows": "Ferestre", "unknownError": "A apărut o eroare necunoscută", - "vaultAuthDescription": "S-a deschis o fereastră pentru a vă conecta la HashiCorp Vault. Finalizați conectarea acolo; această conexiune va continua automat.", - "vaultAuthFailed": "Autentificarea în seif a eșuat. Vă rugăm să încercați din nou.", - "vaultAuthTitle": "Este necesară conectarea la seif", - "vaultReopen": "Redeschideți fereastra de conectare", "websocketAbnormalClose": "Conexiunea s-a închis pe neașteptate. Acest lucru se poate datora unei probleme de configurare a proxy-ului invers sau a SSL. Vă rugăm să verificați jurnalele serverului.", "websocketError": "Eroare de conexiune WebSocket" }, diff --git a/plugins/ssh-terminal/locales/translated/ru_RU.json b/plugins/ssh-terminal/locales/translated/ru_RU.json index 0136d34ac..307253e1e 100644 --- a/plugins/ssh-terminal/locales/translated/ru_RU.json +++ b/plugins/ssh-terminal/locales/translated/ru_RU.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} окно", "tmuxWindows": "Окна", "unknownError": "Произошла неизвестная ошибка", - "vaultAuthDescription": "Открыто окно для входа в HashiCorp Vault. Завершите вход там; подключение продолжится автоматически.", - "vaultAuthFailed": "Аутентификация Vault не удалась. Попробуйте снова.", - "vaultAuthTitle": "Требуется вход в Vault", - "vaultReopen": "Повторно открыть окно входа", "websocketAbnormalClose": "Соединение неожиданно закрыто. Возможно, проблема с обратным прокси или конфигурацией SSL. Проверьте журналы сервера.", "websocketError": "Ошибка подключения WebSocket" }, diff --git a/plugins/ssh-terminal/locales/translated/sr_SP.json b/plugins/ssh-terminal/locales/translated/sr_SP.json index 655d8ab56..af88cde06 100644 --- a/plugins/ssh-terminal/locales/translated/sr_SP.json +++ b/plugins/ssh-terminal/locales/translated/sr_SP.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} прозор", "tmuxWindows": "Прозори", "unknownError": "Дошло је до непознате грешке", - "vaultAuthDescription": "Отворио се прозор за пријављивање на HashiCorp Vault. Завршите пријављивање тамо; ова веза ће се аутоматски наставити.", - "vaultAuthFailed": "Аутентификација трезора није успела. Молимо покушајте поново.", - "vaultAuthTitle": "Потребно је пријављивање у трезор", - "vaultReopen": "Поново отвори прозор за пријављивање", "websocketAbnormalClose": "Веза је неочекивано прекинута. Ово може бити због проблема са обрнутим проксијем или SSL конфигурацијом. Молимо вас да проверите логове сервера.", "websocketError": "Грешка у повезивању са WebSocket-ом" }, diff --git a/plugins/ssh-terminal/locales/translated/sv_SE.json b/plugins/ssh-terminal/locales/translated/sv_SE.json index 6ccfc623d..266a74daf 100644 --- a/plugins/ssh-terminal/locales/translated/sv_SE.json +++ b/plugins/ssh-terminal/locales/translated/sv_SE.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} fönster", "tmuxWindows": "Fönster", "unknownError": "Okänt fel inträffade", - "vaultAuthDescription": "Ett fönster har öppnats för att logga in på HashiCorp Vault. Slutför inloggningen där; anslutningen fortsätter automatiskt.", - "vaultAuthFailed": "Valvautentisering misslyckades. Försök igen.", - "vaultAuthTitle": "Inloggning till arkivet krävs", - "vaultReopen": "Öppna inloggningsfönstret igen", "websocketAbnormalClose": "Anslutningen stängdes oväntat. Detta kan bero på ett problem med omvänd proxy eller SSL-konfiguration. Kontrollera serverloggarna.", "websocketError": "WebSocket-anslutningsfel" }, diff --git a/plugins/ssh-terminal/locales/translated/th_TH.json b/plugins/ssh-terminal/locales/translated/th_TH.json index 52a389635..51c8836c0 100644 --- a/plugins/ssh-terminal/locales/translated/th_TH.json +++ b/plugins/ssh-terminal/locales/translated/th_TH.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} หน้าต่าง", "tmuxWindows": "วินโดวส์", "unknownError": "เกิดข้อผิดพลาดที่ไม่ทราบสาเหตุ", - "vaultAuthDescription": "หน้าต่างสำหรับเข้าสู่ระบบ HashiCorp Vault ได้เปิดขึ้นแล้ว โปรดทำการเข้าสู่ระบบให้เสร็จสมบูรณ์ การเชื่อมต่อนี้จะดำเนินการต่อโดยอัตโนมัติ", - "vaultAuthFailed": "การตรวจสอบสิทธิ์ Vault ล้มเหลว โปรดลองอีกครั้ง", - "vaultAuthTitle": "ต้องลงชื่อเข้าใช้ Vault", - "vaultReopen": "เปิดหน้าต่างเข้าสู่ระบบอีกครั้ง", "websocketAbnormalClose": "การเชื่อมต่อถูกตัดโดยไม่คาดคิด อาจเกิดจากปัญหาเกี่ยวกับรีเวิร์สพร็อกซีหรือการกำหนดค่า SSL โปรดตรวจสอบบันทึกของเซิร์ฟเวอร์", "websocketError": "ข้อผิดพลาดในการเชื่อมต่อ WebSocket" }, diff --git a/plugins/ssh-terminal/locales/translated/tr_TR.json b/plugins/ssh-terminal/locales/translated/tr_TR.json index 55eff8eb4..5aca43c83 100644 --- a/plugins/ssh-terminal/locales/translated/tr_TR.json +++ b/plugins/ssh-terminal/locales/translated/tr_TR.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} pencere", "tmuxWindows": "Pencereler", "unknownError": "Bilinmeyen bir hata oluştu", - "vaultAuthDescription": "HashiCorp Vault'ta oturum açmak için bir pencere açıldı. Orada oturum açmayı tamamlayın; bu bağlantı otomatik olarak devam edecek.", - "vaultAuthFailed": "Vault kimlik doğrulaması başarısız oldu. Lütfen tekrar deneyin.", - "vaultAuthTitle": "Vault Oturum Açması Gerekli", - "vaultReopen": "Oturum açma penceresini yeniden aç", "websocketAbnormalClose": "Bağlantı beklenmedik şekilde kapandı. Bu, ters proxy veya SSL yapılandırma sorunundan kaynaklanabilir. Lütfen sunucu günlüklerini kontrol edin.", "websocketError": "WebSocket bağlantı hatası" }, diff --git a/plugins/ssh-terminal/locales/translated/uk_UA.json b/plugins/ssh-terminal/locales/translated/uk_UA.json index 8ba161377..094cce3ad 100644 --- a/plugins/ssh-terminal/locales/translated/uk_UA.json +++ b/plugins/ssh-terminal/locales/translated/uk_UA.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "вікно {{count}}", "tmuxWindows": "Вікна", "unknownError": "Сталася невідома помилка", - "vaultAuthDescription": "Відкрилося вікно для входу в HashiCorp Vault. Завершіть вхід там; це з’єднання буде продовжено автоматично.", - "vaultAuthFailed": "Не вдалося виконати автентифікацію сховища. Спробуйте ще раз.", - "vaultAuthTitle": "Потрібен вхід до Сейфа", - "vaultReopen": "Знову відкрити вікно входу", "websocketAbnormalClose": "З’єднання несподівано завершилося. Можливо, це пов’язано зі зворотним проксі-сервером або проблемою конфігурації SSL. Перевірте журнали сервера.", "websocketError": "Помилка підключення WebSocket" }, diff --git a/plugins/ssh-terminal/locales/translated/vi_VN.json b/plugins/ssh-terminal/locales/translated/vi_VN.json index 440c2bcc9..c41177ba9 100644 --- a/plugins/ssh-terminal/locales/translated/vi_VN.json +++ b/plugins/ssh-terminal/locales/translated/vi_VN.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} cửa sổ", "tmuxWindows": "Cửa sổ", "unknownError": "Lỗi không xác định đã xảy ra", - "vaultAuthDescription": "Một cửa sổ đã mở để đăng nhập vào HashiCorp Vault. Hoàn tất đăng nhập ở đó; kết nối này sẽ tự động tiếp tục.", - "vaultAuthFailed": "Xác thực Vault thất bại. Vui lòng thử lại.", - "vaultAuthTitle": "Yêu cầu đăng nhập Vault", - "vaultReopen": "Mở lại cửa sổ đăng nhập", "websocketAbnormalClose": "Kết nối đóng đột ngột. Có thể do sự cố cấu hình reverse proxy hoặc SSL. Vui lòng kiểm tra nhật ký máy chủ.", "websocketError": "Lỗi kết nối WebSocket" }, diff --git a/plugins/ssh-terminal/locales/translated/zh_CN.json b/plugins/ssh-terminal/locales/translated/zh_CN.json index 4a63b38ca..0709194aa 100644 --- a/plugins/ssh-terminal/locales/translated/zh_CN.json +++ b/plugins/ssh-terminal/locales/translated/zh_CN.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} 个窗口", "tmuxWindows": "窗口", "unknownError": "发生未知错误", - "vaultAuthDescription": "已打开窗口以登录 HashiCorp Vault。在该处完成登录;此连接将自动继续。", - "vaultAuthFailed": "Vault 认证失败,请重试。", - "vaultAuthTitle": "需要 Vault 登录", - "vaultReopen": "重新打开登录窗口", "websocketAbnormalClose": "连接意外关闭。这可能是因为反向代理或SSL配置问题。请检查服务器日志。", "websocketError": "WebSocket 连接错误" }, diff --git a/plugins/ssh-terminal/locales/translated/zh_TW.json b/plugins/ssh-terminal/locales/translated/zh_TW.json index 63cda0985..a542c77ff 100644 --- a/plugins/ssh-terminal/locales/translated/zh_TW.json +++ b/plugins/ssh-terminal/locales/translated/zh_TW.json @@ -74,10 +74,6 @@ "tmuxWindowCount": "{{count}} 個視窗", "tmuxWindows": "視窗", "unknownError": "發生未知錯誤", - "vaultAuthDescription": "已開啟視窗以登入 HashiCorp Vault。在該處完成登入;此連線將自動繼續。", - "vaultAuthFailed": "Vault 驗證失敗。請再試一次。", - "vaultAuthTitle": "需要 Vault 登入", - "vaultReopen": "重新開啟登入視窗", "websocketAbnormalClose": "連線意外關閉。這可能是由於反向代理或 SSL 設定問題所導致。請檢查伺服器記錄。", "websocketError": "WebSocket 連線錯誤" }, diff --git a/plugins/ssh-terminal/src/backend/terminal-socket.ts b/plugins/ssh-terminal/src/backend/terminal-socket.ts index 6435aa57a..24d97b8b2 100644 --- a/plugins/ssh-terminal/src/backend/terminal-socket.ts +++ b/plugins/ssh-terminal/src/backend/terminal-socket.ts @@ -1141,7 +1141,7 @@ export function createTerminalSocket(deps: TerminalSocketDeps) { /** * Starts the browser sign-in behind an "_auth_required" * message. The provider for the host's auth type wins; otherwise the first - * provider that owns this interaction (Vault attaches to any auth type). + * provider that owns this interaction. */ async function startAuthInteraction( interaction: string, diff --git a/plugins/ssh-terminal/src/frontend/terminal/Terminal.tsx b/plugins/ssh-terminal/src/frontend/terminal/Terminal.tsx index bdd4168a2..2a8d5419f 100644 --- a/plugins/ssh-terminal/src/frontend/terminal/Terminal.tsx +++ b/plugins/ssh-terminal/src/frontend/terminal/Terminal.tsx @@ -342,15 +342,6 @@ const TerminalInner = forwardRef( const currentHostIdRef = useRef(null); const currentHostConfigRef = useRef(null); - // Vault SSH signer interactive OIDC flow - const [vaultDialog, setVaultDialog] = useState<{ - stage: "waiting" | "error"; - error?: string; - } | null>(null); - const vaultFailedRef = useRef(false); - const vaultPopupRef = useRef(null); - const vaultTimeoutRef = useRef(null); - const [hostKeyVerification, setHostKeyVerification] = useState<{ isOpen: boolean; scenario: "new" | "changed"; @@ -1909,7 +1900,6 @@ const TerminalInner = forwardRef( updateConnectionError(errorMessage); setIsConnecting(false); } else if (msg.type === "connected") { - vaultFailedRef.current = false; wasConnectedRef.current = true; setIsConnected(true); setIsConnecting(false); @@ -2104,86 +2094,6 @@ const TerminalInner = forwardRef( webSocketRef.current.close(); } }, 300000); - } else if (msg.type === "vault_auth_required") { - if (connectionTimeoutRef.current) { - clearTimeout(connectionTimeoutRef.current); - connectionTimeoutRef.current = null; - } - if (vaultFailedRef.current) { - setVaultDialog({ - stage: "error", - error: t("terminal.vaultAuthFailed"), - }); - updateConnectionError(t("terminal.vaultAuthFailed")); - addLog({ - type: "error", - stage: "auth", - message: t("terminal.vaultAuthFailed"), - }); - } else { - vaultFailedRef.current = true; - webSocketRef.current?.send( - JSON.stringify({ - type: "vault_start_auth", - data: { hostId: msg.hostId }, - }), - ); - } - } else if (msg.type === "vault_auth_url") { - if (connectionErrorRef.current) return; - try { - vaultPopupRef.current = window.open( - msg.url, - "termix-vault-oidc", - "width=540,height=720", - ); - } catch { - vaultPopupRef.current = null; - } - setVaultDialog({ stage: "waiting" }); - if (vaultTimeoutRef.current) clearTimeout(vaultTimeoutRef.current); - vaultTimeoutRef.current = setTimeout(() => { - setVaultDialog(null); - webSocketRef.current?.close(); - }, 300000); - } else if (msg.type === "vault_completed") { - if (vaultTimeoutRef.current) { - clearTimeout(vaultTimeoutRef.current); - vaultTimeoutRef.current = null; - } - try { - vaultPopupRef.current?.close(); - } catch { - // popup may already be closed - } - setVaultDialog(null); - if (webSocketRef.current && terminal) { - webSocketRef.current.send( - JSON.stringify({ - type: "vault_auth_completed", - data: { - hostId: currentHostIdRef.current, - cols: terminal.cols || 80, - rows: terminal.rows || 24, - hostConfig: currentHostConfigRef.current, - }, - }), - ); - } - } else if (msg.type === "vault_error") { - if (connectionErrorRef.current) return; - vaultFailedRef.current = true; - if (vaultTimeoutRef.current) { - clearTimeout(vaultTimeoutRef.current); - vaultTimeoutRef.current = null; - } - try { - vaultPopupRef.current?.close(); - } catch { - // popup may already be closed - } - setVaultDialog({ stage: "error", error: msg.error }); - setIsConnecting(false); } else if (msg.type === "keyboard_interactive_available") { setKeyboardInteractiveDetected(true); setIsConnecting(false); @@ -2240,7 +2150,6 @@ const TerminalInner = forwardRef( } } else if (msg.type === "sessionAttached") { isAttachingSessionRef.current = false; - vaultFailedRef.current = false; wasConnectedRef.current = true; setIsConnected(true); setIsConnecting(false); @@ -3884,70 +3793,6 @@ const TerminalInner = forwardRef( props={overlayProps as unknown as Record} /> - {vaultDialog && ( -
-
-

- {vaultDialog.stage === "error" - ? t("terminal.vaultAuthFailed") - : t("terminal.vaultAuthTitle")} -

- {vaultDialog.stage === "error" ? ( -

- {vaultDialog.error || t("terminal.vaultAuthFailed")} -

- ) : ( -

- {t("terminal.vaultAuthDescription")} -

- )} -
- {vaultDialog.stage === "waiting" && ( - - )} - -
-
-
- )} - {hostKeyVerification?.isOpen && ( /oidc/auth_url`, +`auth//oidc/callback`, `/sign/`). No Vault token, +AppRole secret or long-lived key is ever stored. + +## Profiles + +A profile holds the connection settings only: the Vault address and +namespace, the OIDC mount and role, the SSH secrets mount and signer role, +the valid principals and the key type. Pick one in the host editor after +choosing "Vault" as the authentication type, and manage profiles from the +same place. Users with the "Share Vault profiles" permission can share a +profile with everyone. + +## Settings + +Admin settings, under Settings > Plugins > HashiCorp Vault: + +- **Redirect URI**: add it to `allowed_redirect_uris` in each Vault OIDC + role: `/plugin-api/vault/oidc/callback`, or + `/vault/oidc/callback` while "Use the old redirect URI" is on. + +An install upgraded from 2.8 that used Vault keeps the old redirect URI +turned on. diff --git a/plugins/vault/locales/en.json b/plugins/vault/locales/en.json new file mode 100644 index 000000000..391846b5e --- /dev/null +++ b/plugins/vault/locales/en.json @@ -0,0 +1,69 @@ +{ + "editor": { + "profile": "Vault Signer Profile", + "selectProfile": "Select a Vault profile...", + "hint": "Settings come from the shared profile; you'll sign in to Vault via OIDC when you connect. No secrets are stored.", + "docs": "View docs", + "sharedProfile": "{{name}} (shared)" + }, + "profiles": { + "new": "New profile", + "manage": "Manage Vault profiles", + "name": "Friendly Name", + "vaultAddr": "Vault Address", + "namespace": "Namespace", + "oidcMount": "OIDC Auth Mount", + "oidcRole": "OIDC Role", + "sshMount": "SSH Secrets Mount", + "sshRole": "SSH Signer Role", + "validPrincipals": "Valid Principals", + "keyType": "Ephemeral Key Type", + "shareWithAll": "Share with all users", + "create": "Create profile", + "created": "Vault profile created", + "saved": "Vault profile saved", + "deleted": "Vault profile deleted", + "saveFailed": "Failed to save Vault profile", + "deleteFailed": "Failed to delete Vault profile", + "save": "Save profile", + "validationError": "Name, Vault address and SSH signer role are required", + "none": "No Vault profiles yet.", + "sharedBadge": "shared", + "edit": "Edit", + "delete": "Delete" + }, + "hosts": { + "filterAuthVault": "Vault" + }, + "dialog": { + "title": "Vault sign-in required", + "description": "A window has opened to sign in to HashiCorp Vault. Complete the sign-in there; this connection will continue automatically.", + "failed": "Vault authentication failed. Please try again.", + "reopen": "Reopen sign-in window" + }, + "permissions": { + "use": { + "title": "Use Vault SSH signing", + "description": "Create Vault signer profiles and connect to hosts that use Vault." + }, + "share": { + "title": "Share Vault profiles", + "description": "Mark a Vault signer profile as shared so every user can pick it." + } + }, + "settings": { + "redirectUri": { + "label": "Redirect URI", + "description": "Add this URL to allowed_redirect_uris in each Vault OIDC role that Termix signs in with." + }, + "legacyCallback": { + "label": "Use the old redirect URI", + "description": "Keep sending /vault/oidc/callback, the redirect URI used before 2.9. Turn this off once your Vault roles allow the new one." + }, + "host": { + "profileId": { + "label": "Vault signer profile" + } + } + } +} diff --git a/plugins/vault/locales/translated/af_ZA.json b/plugins/vault/locales/translated/af_ZA.json new file mode 100644 index 000000000..8f63e54cf --- /dev/null +++ b/plugins/vault/locales/translated/af_ZA.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Kluis Ondertekenaar Profiel", + "selectProfile": "Kies 'n kluisprofiel...", + "hint": "Instellings kom van die gedeelde profiel; jy sal by Vault aanmeld via OIDC wanneer jy koppel. Geen geheime word gestoor nie.", + "docs": "Bekyk dokumente" + }, + "profiles": { + "new": "Nuwe profiel", + "manage": "Bestuur Vault-profiele", + "name": "Vriendelike Naam", + "vaultAddr": "Kluisadres", + "namespace": "Naamruimte", + "oidcMount": "OIDC-magtigingsmontering", + "oidcRole": "OIDC-rol", + "sshMount": "SSH-geheime berg", + "sshRole": "SSH-ondertekenaarrol", + "validPrincipals": "Geldige Prinsipale", + "keyType": "Efemere Sleuteltipe", + "shareWithAll": "Deel met alle gebruikers", + "create": "Skep profiel", + "created": "Kluisprofiel geskep", + "saved": "Kluisprofiel gestoor", + "deleted": "Kluisprofiel uitgevee", + "saveFailed": "Kon nie kluisprofiel stoor nie", + "deleteFailed": "Kon nie kluisprofiel uitvee nie", + "save": "Stoor profiel", + "validationError": "Naam, kluisadres en SSH-ondertekenrol word vereis", + "none": "Geen Vault-profiele nog nie.", + "sharedBadge": "gedeel" + }, + "dialog": { + "title": "Kluisaanmelding vereis", + "description": "'n Venster het oopgemaak om by HashiCorp Vault aan te meld. Voltooi die aanmelding daar; hierdie verbinding sal outomaties voortgaan.", + "failed": "Kluisverifikasie het misluk. Probeer asseblief weer.", + "reopen": "Maak aanmeldvenster weer oop" + } +} diff --git a/plugins/vault/locales/translated/ar_SA.json b/plugins/vault/locales/translated/ar_SA.json new file mode 100644 index 000000000..c5b14ff8e --- /dev/null +++ b/plugins/vault/locales/translated/ar_SA.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "ملف توقيع Vault", + "selectProfile": "اختيار ملف توقيع Vault...", + "hint": "تأتي الإعدادات من الملف المشترك؛ ستقوم بتسجيل الدخول إلى Vault عبر OIDC عند الاتصال. لا يتم تخزين أي أسرار.", + "docs": "عرض المستندات" + }, + "profiles": { + "new": "ملف جديد", + "manage": "إدارة ملفات Vault", + "name": "الاسم المألوف", + "vaultAddr": "عنوان Vault", + "namespace": "مساحة الاسم", + "oidcMount": "تثبيت مصادقة OIDC", + "oidcRole": "دور OIDC", + "sshMount": "تثبيت أسرار SSH", + "sshRole": "دور موقع SSH", + "validPrincipals": "المستخدمون المسموح بهم", + "keyType": "نوع المفتاح المؤقت", + "shareWithAll": "مشاركة مع جميع المستخدمين", + "create": "إنشاء ملف تعريف", + "created": "تم إنشاء ملف تعريف Vault", + "saved": "تم حفظ ملف تعريف Vault", + "deleted": "تم حذف ملف تعريف Vault", + "saveFailed": "فشل حفظ ملف تعريف Vault", + "deleteFailed": "فشل حذف ملف تعريف Vault", + "save": "حفظ ملف التعريف", + "validationError": "الاسم وعنوان Vault ودور الموقّع SSH مطلوب", + "none": "لا توجد ملفات تعريف Vault بعد.", + "sharedBadge": "مشترك" + }, + "dialog": { + "title": "مطلوب تسجيل الدخول إلى Vault", + "description": "تم فتح نافذة لتسجيل الدخول إلى HashiCorp Vault. أكمل تسجيل الدخول هناك؛ سيستمر هذا الاتصال تلقائيًا.", + "failed": "فشلت مصادقة Vault. يرجى المحاولة مرة أخرى.", + "reopen": "أعد فتح نافذة تسجيل الدخول" + } +} diff --git a/plugins/vault/locales/translated/bg_BG.json b/plugins/vault/locales/translated/bg_BG.json new file mode 100644 index 000000000..f4662a9a6 --- /dev/null +++ b/plugins/vault/locales/translated/bg_BG.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Профил на подписващ в трезора", + "selectProfile": "Изберете профил в трезора...", + "hint": "Настройките идват от споделения профил; ще влезете в Vault чрез OIDC, когато се свържете. Не се съхраняват тайни.", + "docs": "Преглед на документи" + }, + "profiles": { + "new": "Нов профил", + "manage": "Управление на профили в хранилището", + "name": "Приятелско име", + "vaultAddr": "Адрес на трезора", + "namespace": "Именно пространство", + "oidcMount": "OIDC Authentication Mount", + "oidcRole": "Роля на OIDC", + "sshMount": "SSH Secrets Mount", + "sshRole": "Роля на SSH подписващ", + "validPrincipals": "Валидни принципали", + "keyType": "Тип ефимерен ключ", + "shareWithAll": "Споделяне с всички потребители", + "create": "Създаване на профил", + "created": "Профилът на трезора е създаден", + "saved": "Профилът на трезора е запазен", + "deleted": "Профилът в трезора е изтрит", + "saveFailed": "Запазването на профила в Трезора не бе успешно", + "deleteFailed": "Изтриването на профила в Vault не бе успешно", + "save": "Запазване на профил", + "validationError": "Име, адрес на трезора и роля на SSH подписващ са задължителни", + "none": "Все още няма профили в Трезора.", + "sharedBadge": "споделено" + }, + "dialog": { + "title": "Изисква се влизане в трезора", + "description": "Отвори се прозорец за влизане в HashiCorp Vault. Завършете влизането там; тази връзка ще продължи автоматично.", + "failed": "Удостоверяването на трезора не бе успешно. Моля, опитайте отново.", + "reopen": "Отваряне отново на прозореца за влизане" + } +} diff --git a/plugins/vault/locales/translated/bn_BD.json b/plugins/vault/locales/translated/bn_BD.json new file mode 100644 index 000000000..154e6344c --- /dev/null +++ b/plugins/vault/locales/translated/bn_BD.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "ভল্ট স্বাক্ষরকারীর প্রোফাইল", + "selectProfile": "একটি ভল্ট প্রোফাইল নির্বাচন করুন...", + "hint": "সেটিংস শেয়ার করা প্রোফাইল থেকে আসে; সংযোগ করার সময় আপনি OIDC-এর মাধ্যমে ভল্টে সাইন ইন করবেন। কোনো গোপনীয় তথ্য সংরক্ষিত থাকে না।", + "docs": "নথি দেখুন" + }, + "profiles": { + "new": "নতুন প্রোফাইল", + "manage": "ভল্ট প্রোফাইলগুলি পরিচালনা করুন", + "name": "বন্ধুত্বপূর্ণ নাম", + "vaultAddr": "ভল্ট ঠিকানা", + "namespace": "নেমস্পেস", + "oidcMount": "OIDC অথোরাইজেশন মাউন্ট", + "oidcRole": "OIDC ভূমিকা", + "sshMount": "SSH সিক্রেটস মাউন্ট", + "sshRole": "SSH স্বাক্ষরকারীর ভূমিকা", + "validPrincipals": "বৈধ অধ্যক্ষরা", + "keyType": "ক্ষণস্থায়ী কী টাইপ", + "shareWithAll": "সকল ব্যবহারকারীর সাথে শেয়ার করুন", + "create": "প্রোফাইল তৈরি করুন", + "created": "ভল্ট প্রোফাইল তৈরি করা হয়েছে", + "saved": "ভল্ট প্রোফাইল সংরক্ষিত হয়েছে", + "deleted": "ভল্ট প্রোফাইল মুছে ফেলা হয়েছে", + "saveFailed": "ভল্ট প্রোফাইল সংরক্ষণ করতে ব্যর্থ হয়েছে", + "deleteFailed": "ভল্ট প্রোফাইল মুছে ফেলা ব্যর্থ হয়েছে", + "save": "প্রোফাইল সংরক্ষণ করুন", + "validationError": "নাম, ভল্ট অ্যাড্রেস এবং SSH সাইনার রোল প্রয়োজন।", + "none": "এখনো কোনো ভল্ট প্রোফাইল তৈরি হয়নি।", + "sharedBadge": "শেয়ার করা" + }, + "dialog": { + "title": "ভল্টে সাইন-ইন করা আবশ্যক", + "description": "হ্যাশিকর্প ভল্টে সাইন ইন করার জন্য একটি উইন্ডো খুলেছে। সেখানে সাইন-ইন সম্পন্ন করুন; এই সংযোগটি স্বয়ংক্রিয়ভাবে চলতে থাকবে।", + "failed": "ভল্ট প্রমাণীকরণ ব্যর্থ হয়েছে। অনুগ্রহ করে আবার চেষ্টা করুন।", + "reopen": "সাইন-ইন উইন্ডো পুনরায় খুলুন" + } +} diff --git a/plugins/vault/locales/translated/ca_ES.json b/plugins/vault/locales/translated/ca_ES.json new file mode 100644 index 000000000..e4a65cf60 --- /dev/null +++ b/plugins/vault/locales/translated/ca_ES.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Perfil del signant de la caixa forta", + "selectProfile": "Seleccioneu un perfil de Vault...", + "hint": "La configuració prové del perfil compartit; iniciaràs la sessió a Vault mitjançant OIDC quan et connectis. No s'emmagatzemen secrets.", + "docs": "Veure documents" + }, + "profiles": { + "new": "Nou perfil", + "manage": "Gestiona els perfils de Vault", + "name": "Nom amigable", + "vaultAddr": "Adreça de la caixa forta", + "namespace": "Espai de noms", + "oidcMount": "Muntatge d'autenticació OIDC", + "oidcRole": "Rol de l'OIDC", + "sshMount": "Muntanya de Secrets SSH", + "sshRole": "Rol de signant SSH", + "validPrincipals": "Principals vàlids", + "keyType": "Tipus de clau efímera", + "shareWithAll": "Comparteix amb tots els usuaris", + "create": "Crea un perfil", + "created": "Perfil de la caixa forta creat", + "saved": "Perfil de la caixa forta desat", + "deleted": "Perfil de la caixa forta suprimit", + "saveFailed": "No s'ha pogut desar el perfil de Vault", + "deleteFailed": "No s'ha pogut suprimir el perfil de Vault", + "save": "Desa el perfil", + "validationError": "El nom, l'adreça de la caixa forta i el rol de signant SSH són obligatoris.", + "none": "Encara no hi ha perfils de Vault.", + "sharedBadge": "compartit" + }, + "dialog": { + "title": "Cal iniciar sessió a la caixa forta", + "description": "S'ha obert una finestra per iniciar la sessió a HashiCorp Vault. Completeu l'inici de sessió allà; aquesta connexió continuarà automàticament.", + "failed": "L'autenticació de la caixa forta ha fallat. Torna-ho a provar.", + "reopen": "Torna a obrir la finestra d'inici de sessió" + } +} diff --git a/plugins/vault/locales/translated/cs_CZ.json b/plugins/vault/locales/translated/cs_CZ.json new file mode 100644 index 000000000..b460f86cb --- /dev/null +++ b/plugins/vault/locales/translated/cs_CZ.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Profil podepisujícího trezoru", + "selectProfile": "Vyberte profil úložiště...", + "hint": "Nastavení pocházejí ze sdíleného profilu; po připojení se do úložiště přihlásíte přes OIDC. Žádné tajné kódy se neukládají.", + "docs": "Zobrazit dokumenty" + }, + "profiles": { + "new": "Nový profil", + "manage": "Správa profilů úložiště", + "name": "Přátelský název", + "vaultAddr": "Adresa trezoru", + "namespace": "Jmenný prostor", + "oidcMount": "Připojení autorizace OIDC", + "oidcRole": "Role OIDC", + "sshMount": "SSH tajemství Mount", + "sshRole": "Role SSH podepisujícího", + "validPrincipals": "Platné principy", + "keyType": "Typ dočasného klíče", + "shareWithAll": "Sdílet se všemi uživateli", + "create": "Vytvořit profil", + "created": "Profil úložiště vytvořen", + "saved": "Profil trezoru uložen", + "deleted": "Profil trezoru smazán", + "saveFailed": "Uložení profilu úložiště se nezdařilo.", + "deleteFailed": "Profil v úložišti se nepodařilo smazat.", + "save": "Uložit profil", + "validationError": "Jméno, adresa trezoru a role SSH signatáře jsou povinné.", + "none": "Zatím žádné profily v úložišti Vault.", + "sharedBadge": "sdílené" + }, + "dialog": { + "title": "Vyžaduje se přihlášení do trezoru", + "description": "Otevřelo se okno pro přihlášení do HashiCorp Vault. Dokončete přihlášení tam; toto připojení bude automaticky pokračovat.", + "failed": "Ověření v trezoru se nezdařilo. Zkuste to prosím znovu.", + "reopen": "Znovu otevřít přihlašovací okno" + } +} diff --git a/plugins/vault/locales/translated/da_DK.json b/plugins/vault/locales/translated/da_DK.json new file mode 100644 index 000000000..1a6ee51d7 --- /dev/null +++ b/plugins/vault/locales/translated/da_DK.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Profil for underskriver af arkiv", + "selectProfile": "Vælg en Vault-profil...", + "hint": "Indstillingerne kommer fra den delte profil; du logger ind på Vault via OIDC, når du opretter forbindelse. Der gemmes ingen hemmeligheder.", + "docs": "Se dokumenter" + }, + "profiles": { + "new": "Ny profil", + "manage": "Administrer Vault-profiler", + "name": "Venligt navn", + "vaultAddr": "Adresse i boksen", + "namespace": "Navnerum", + "oidcMount": "OIDC-godkendelsesmontering", + "oidcRole": "OIDC-rolle", + "sshMount": "SSH-hemmeligheder monteres", + "sshRole": "SSH-underskriverrolle", + "validPrincipals": "Gyldige principper", + "keyType": "Kortvarig nøgletype", + "shareWithAll": "Del med alle brugere", + "create": "Opret profil", + "created": "Vault-profil oprettet", + "saved": "Vault-profil gemt", + "deleted": "Vault-profil slettet", + "saveFailed": "Kunne ikke gemme Vault-profilen", + "deleteFailed": "Kunne ikke slette Vault-profilen", + "save": "Gem profil", + "validationError": "Navn, Vault-adresse og SSH-underskriverrolle er påkrævet", + "none": "Ingen Vault-profiler endnu.", + "sharedBadge": "delt" + }, + "dialog": { + "title": "Login til Vault kræves", + "description": "Et vindue er åbnet, hvor du kan logge ind på HashiCorp Vault. Fuldfør loginprocessen der; forbindelsen fortsætter automatisk.", + "failed": "Godkendelse af arkiv mislykkedes. Prøv igen.", + "reopen": "Genåbn loginvinduet" + } +} diff --git a/plugins/vault/locales/translated/de_DE.json b/plugins/vault/locales/translated/de_DE.json new file mode 100644 index 000000000..4bd6e0038 --- /dev/null +++ b/plugins/vault/locales/translated/de_DE.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Vault-Signer-Profil", + "selectProfile": "Vault-Profil auswählen...", + "hint": "Die Einstellungen stammen aus dem freigegebenen Profil; Sie melden sich beim Verbinden per OIDC bei Vault an. Es werden keine Geheimnisse gespeichert.", + "docs": "Dokumentation anzeigen" + }, + "profiles": { + "new": "Neues Profil", + "manage": "Vault-Profile verwalten", + "name": "Anzeigename", + "vaultAddr": "Vault-Adresse", + "namespace": "Namespace", + "oidcMount": "OIDC-Auth-Mount", + "oidcRole": "OIDC-Rolle", + "sshMount": "SSH-Secrets-Mount", + "sshRole": "SSH-Signer-Rolle", + "validPrincipals": "Gültige Principals", + "keyType": "Ephemerer Schlüsseltyp", + "shareWithAll": "Mit allen Benutzern teilen", + "create": "Profil erstellen", + "created": "Vault-Profil erstellt", + "saved": "Vault-Profil gespeichert", + "deleted": "Vault-Profil gelöscht", + "saveFailed": "Fehler beim Speichern des Vault-Profils", + "deleteFailed": "Fehler beim Löschen des Vault-Profils", + "save": "Profil speichern", + "validationError": "Name, Vault-Adresse und SSH-Signer-Rolle sind erforderlich", + "none": "Noch keine Vault-Profile.", + "sharedBadge": "geteilt" + }, + "dialog": { + "title": "Vault-Anmeldung erforderlich", + "description": "Ein Fenster zur Anmeldung bei HashiCorp Vault wurde geöffnet. Schließen Sie die Anmeldung dort ab; diese Verbindung wird automatisch fortgesetzt.", + "failed": "Vault-Authentifizierung fehlgeschlagen. Bitte versuchen Sie es erneut.", + "reopen": "Anmeldefenster erneut öffnen" + } +} diff --git a/plugins/vault/locales/translated/el_GR.json b/plugins/vault/locales/translated/el_GR.json new file mode 100644 index 000000000..53fe66301 --- /dev/null +++ b/plugins/vault/locales/translated/el_GR.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Προφίλ Υπογράφοντος Vault", + "selectProfile": "Επιλέξτε ένα προφίλ Vault...", + "hint": "Οι ρυθμίσεις προέρχονται από το κοινόχρηστο προφίλ. Θα συνδεθείτε στο Vault μέσω OIDC όταν συνδεθείτε. Δεν αποθηκεύονται μυστικά.", + "docs": "Προβολή εγγράφων" + }, + "profiles": { + "new": "Νέο προφίλ", + "manage": "Διαχείριση προφίλ Vault", + "name": "Φιλικό όνομα", + "vaultAddr": "Διεύθυνση θησαυροφυλακίου", + "namespace": "Χώρος ονομάτων", + "oidcMount": "Μοντάζ ελέγχου ταυτότητας OIDC", + "oidcRole": "Ρόλος OIDC", + "sshMount": "SSH Secrets Mount", + "sshRole": "Ρόλος Υπογράφοντος SSH", + "validPrincipals": "Έγκυρες Αρχές", + "keyType": "Εφήμερος Τύπος Κλειδιού", + "shareWithAll": "Κοινοποίηση με όλους τους χρήστες", + "create": "Δημιουργία προφίλ", + "created": "Δημιουργήθηκε προφίλ Vault", + "saved": "Το προφίλ του Vault αποθηκεύτηκε", + "deleted": "Το προφίλ Vault διαγράφηκε", + "saveFailed": "Αποτυχία αποθήκευσης προφίλ Vault", + "deleteFailed": "Αποτυχία διαγραφής προφίλ Vault", + "save": "Αποθήκευση προφίλ", + "validationError": "Απαιτούνται όνομα, διεύθυνση vault και ρόλος υπογράφοντος SSH", + "none": "Δεν υπάρχουν ακόμη προφίλ Vault.", + "sharedBadge": "κοινόχρηστο" + }, + "dialog": { + "title": "Απαιτείται σύνδεση στο Vault", + "description": "Έχει ανοίξει ένα παράθυρο για να συνδεθείτε στο HashiCorp Vault. Ολοκληρώστε τη σύνδεση εκεί. Αυτή η σύνδεση θα συνεχιστεί αυτόματα.", + "failed": "Ο έλεγχος ταυτότητας του vault απέτυχε. Δοκιμάστε ξανά.", + "reopen": "Επανάνοιγμα παραθύρου σύνδεσης" + } +} diff --git a/plugins/vault/locales/translated/es_ES.json b/plugins/vault/locales/translated/es_ES.json new file mode 100644 index 000000000..ef1f0d7df --- /dev/null +++ b/plugins/vault/locales/translated/es_ES.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Perfil de firmante de Vault", + "selectProfile": "Seleccionar un perfil de Vault...", + "hint": "La configuración proviene del perfil compartido; se iniciará sesión en Vault a través de OIDC al conectarse. No se almacenan secretos.", + "docs": "Ver documentación" + }, + "profiles": { + "new": "Nuevo perfil", + "manage": "Administrar perfiles de Vault", + "name": "Nombre descriptivo", + "vaultAddr": "Dirección de Vault", + "namespace": "Espacio de nombres", + "oidcMount": "Montaje de autenticación OIDC", + "oidcRole": "Rol OIDC", + "sshMount": "Montaje de secretos SSH", + "sshRole": "Rol de firmante SSH", + "validPrincipals": "Principals válidos", + "keyType": "Tipo de clave efímera", + "shareWithAll": "Compartir con todos los usuarios", + "create": "Crear perfil", + "created": "Perfil de Vault creado", + "saved": "Perfil de Vault guardado", + "deleted": "Perfil de Vault eliminado", + "saveFailed": "Error al guardar el perfil de Vault", + "deleteFailed": "Error al eliminar el perfil de Vault", + "save": "Guardar perfil", + "validationError": "Se requieren el nombre, la dirección de Vault y el rol de firmante SSH", + "none": "Aún no hay perfiles de Vault.", + "sharedBadge": "compartido" + }, + "dialog": { + "title": "Inicio de sesión en Vault requerido", + "description": "Se ha abierto una ventana para iniciar sesión en HashiCorp Vault. Completa el inicio de sesión allí; esta conexión continuará automáticamente.", + "failed": "La autenticación de Vault falló. Inténtalo de nuevo.", + "reopen": "Reabrir ventana de inicio de sesión" + } +} diff --git a/plugins/vault/locales/translated/fi_FI.json b/plugins/vault/locales/translated/fi_FI.json new file mode 100644 index 000000000..afedb6714 --- /dev/null +++ b/plugins/vault/locales/translated/fi_FI.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Varaston allekirjoittajan profiili", + "selectProfile": "Valitse holviprofiili...", + "hint": "Asetukset tulevat jaetusta profiilista; kirjaudut Holviin OIDC:n kautta, kun muodostat yhteyden. Salaisuuksia ei tallenneta.", + "docs": "Näytä dokumentit" + }, + "profiles": { + "new": "Uusi profiili", + "manage": "Hallinnoi holviprofiileja", + "name": "Ystävällinen nimi", + "vaultAddr": "Holvin osoite", + "namespace": "Nimiavaruus", + "oidcMount": "OIDC-todennusliitos", + "oidcRole": "OIDC-rooli", + "sshMount": "SSH-salaisuuksien kiinnitys", + "sshRole": "SSH-allekirjoittajan rooli", + "validPrincipals": "Voimassa olevat pääasialliset", + "keyType": "Lyhytaikainen avaintyyppi", + "shareWithAll": "Jaa kaikkien käyttäjien kanssa", + "create": "Luo profiili", + "created": "Holvin profiili luotu", + "saved": "Holvin profiili tallennettu", + "deleted": "Holvin profiili poistettu", + "saveFailed": "Holvin profiilin tallentaminen epäonnistui", + "deleteFailed": "Holviprofiilin poistaminen epäonnistui", + "save": "Tallenna profiili", + "validationError": "Nimi, holvin osoite ja SSH-allekirjoittajan rooli vaaditaan", + "none": "Ei vielä holviprofiileja.", + "sharedBadge": "jaettu" + }, + "dialog": { + "title": "Holviin kirjautuminen vaaditaan", + "description": "Ikkuna HashiCorp-holviin kirjautumista varten on avautunut. Kirjaudu sisään siinä. Yhteys jatkuu automaattisesti.", + "failed": "Holvin todennus epäonnistui. Yritä uudelleen.", + "reopen": "Avaa kirjautumisikkuna uudelleen" + } +} diff --git a/plugins/vault/locales/translated/fr_FR.json b/plugins/vault/locales/translated/fr_FR.json new file mode 100644 index 000000000..5fb46b163 --- /dev/null +++ b/plugins/vault/locales/translated/fr_FR.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Profil de signature Vault", + "selectProfile": "Sélectionner un profil Vault...", + "hint": "Les paramètres proviennent du profil partagé ; vous vous connecterez à Vault via OIDC lors de la connexion. Aucun secret n'est stocké.", + "docs": "Voir la documentation" + }, + "profiles": { + "new": "Nouveau profil", + "manage": "Gérer les profils Vault", + "name": "Nom convivial", + "vaultAddr": "Adresse Vault", + "namespace": "Espace de noms", + "oidcMount": "Point de montage d'authentification OIDC", + "oidcRole": "Rôle OIDC", + "sshMount": "Point de montage des secrets SSH", + "sshRole": "Rôle de signature SSH", + "validPrincipals": "Principaux valides", + "keyType": "Type de clé éphémère", + "shareWithAll": "Partager avec tous les utilisateurs", + "create": "Créer un profil", + "created": "Profil Vault créé", + "saved": "Profil Vault enregistré", + "deleted": "Profil Vault supprimé", + "saveFailed": "Échec de l'enregistrement du profil Vault", + "deleteFailed": "Échec de la suppression du profil Vault", + "save": "Enregistrer le profil", + "validationError": "Le nom, l'adresse Vault et le rôle de signataire SSH sont requis", + "none": "Aucun profil Vault pour le moment", + "sharedBadge": "partagé" + }, + "dialog": { + "title": "Connexion à Vault requise", + "description": "Une fenêtre s'est ouverte pour vous connecter à HashiCorp Vault. Terminez la connexion dans cette fenêtre ; cette connexion continuera automatiquement.", + "failed": "Échec de l'authentification Vault. Veuillez réessayer.", + "reopen": "Rouvrir la fenêtre de connexion" + } +} diff --git a/plugins/vault/locales/translated/he_IL.json b/plugins/vault/locales/translated/he_IL.json new file mode 100644 index 000000000..4b1b6a515 --- /dev/null +++ b/plugins/vault/locales/translated/he_IL.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "פרופיל חותם בכספת", + "selectProfile": "בחר פרופיל כספת...", + "hint": "ההגדרות מגיעות מהפרופיל המשותף; תיכנס לכספת דרך OIDC כשתתחבר. לא נשמרים סודות.", + "docs": "הצג מסמכים" + }, + "profiles": { + "new": "פרופיל חדש", + "manage": "ניהול פרופילי כספת", + "name": "שם ידידותי", + "vaultAddr": "כתובת הכספת", + "namespace": "מרחב השמות", + "oidcMount": "אישור OIDC", + "oidcRole": "תפקיד OIDC", + "sshMount": "סודות SSH מאוורר", + "sshRole": "תפקיד חותם SSH", + "validPrincipals": "עקרונות תקפים", + "keyType": "סוג מפתח זמני", + "shareWithAll": "שתף עם כל המשתמשים", + "create": "צור פרופיל", + "created": "פרופיל הכספת נוצר", + "saved": "פרופיל הכספת נשמר", + "deleted": "פרופיל הכספת נמחק", + "saveFailed": "שמירת פרופיל הכספת נכשלה", + "deleteFailed": "נכשלה מחיקת פרופיל הכספת", + "save": "שמור פרופיל", + "validationError": "שם, כתובת כספת ותפקיד חתימה ב-SSH נדרשים", + "none": "אין עדיין פרופילים בכספת.", + "sharedBadge": "מְשׁוּתָף" + }, + "dialog": { + "title": "נדרשת כניסה לכספת", + "description": "נפתח חלון לכניסה לכספת HashiCorp. השלם את תהליך הכניסה שם; חיבור זה ימשיך אוטומטית.", + "failed": "אימות הכספת נכשל. אנא נסה שוב.", + "reopen": "פתח מחדש את חלון הכניסה" + } +} diff --git a/plugins/vault/locales/translated/hi_IN.json b/plugins/vault/locales/translated/hi_IN.json new file mode 100644 index 000000000..46379e9c4 --- /dev/null +++ b/plugins/vault/locales/translated/hi_IN.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Vault साइनर प्रोफ़ाइल", + "selectProfile": "एक Vault प्रोफ़ाइल चुनें...", + "hint": "सेटिंग साझा प्रोफ़ाइल से आती हैं; कनेक्ट करते समय आप OIDC के माध्यम से Vault में साइन इन करेंगे। कोई सीक्रेट संग्रहीत नहीं किए जाते।", + "docs": "दस्तावेज़ देखें" + }, + "profiles": { + "new": "नई प्रोफ़ाइल", + "manage": "Vault प्रोफ़ाइल प्रबंधित करें", + "name": "अनुकूल नाम", + "vaultAddr": "Vault पता", + "namespace": "नेमस्पेस", + "oidcMount": "OIDC ऑथ माउंट", + "oidcRole": "OIDC रोल", + "sshMount": "SSH सीक्रेट माउंट", + "sshRole": "SSH साइनर रोल", + "validPrincipals": "मान्य प्रिंसिपल", + "keyType": "एफेमरल कुंजी प्रकार", + "shareWithAll": "सभी उपयोगकर्ताओं के साथ साझा करें", + "create": "प्रोफ़ाइल बनाएँ", + "created": "Vault प्रोफ़ाइल बनाई गई", + "saved": "Vault प्रोफ़ाइल सहेजी गई", + "deleted": "Vault प्रोफ़ाइल हटाई गई", + "saveFailed": "Vault प्रोफ़ाइल सहेजने में विफल", + "deleteFailed": "Vault प्रोफ़ाइल हटाने में विफल", + "save": "प्रोफ़ाइल सहेजें", + "validationError": "नाम, Vault पता और SSH हस्ताक्षरकर्ता भूमिका आवश्यक हैं", + "none": "अभी तक कोई Vault प्रोफ़ाइल नहीं है।", + "sharedBadge": "साझा" + }, + "dialog": { + "title": "Vault साइन-इन आवश्यक है", + "description": "HashiCorp Vault में साइन इन करने के लिए एक विंडो खुल गई है। वहाँ साइन-इन पूरा करें; यह कनेक्शन स्वचालित रूप से जारी रहेगा।", + "failed": "Vault प्रमाणीकरण विफल। कृपया पुनः प्रयास करें।", + "reopen": "साइन-इन विंडो फिर से खोलें" + } +} diff --git a/plugins/vault/locales/translated/hu_HU.json b/plugins/vault/locales/translated/hu_HU.json new file mode 100644 index 000000000..788b5289f --- /dev/null +++ b/plugins/vault/locales/translated/hu_HU.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Vault aláírói profil", + "selectProfile": "Válasszon ki egy Vault profilt...", + "hint": "A beállítások a megosztott profilból származnak; a csatlakozáskor OIDC-n keresztül fog bejelentkezni a Vaultba. A rendszer nem tárol titkos adatokat.", + "docs": "Dokumentumok megtekintése" + }, + "profiles": { + "new": "Új profil", + "manage": "Vault profilok kezelése", + "name": "Barátságos név", + "vaultAddr": "Trezor címe", + "namespace": "Névtér", + "oidcMount": "OIDC hitelesítési csatolás", + "oidcRole": "OIDC szerepkör", + "sshMount": "SSH titkok felhúzása", + "sshRole": "SSH aláíró szerepkör", + "validPrincipals": "Érvényes megbízók", + "keyType": "Efemer kulcstípus", + "shareWithAll": "Megosztás az összes felhasználóval", + "create": "Profil létrehozása", + "created": "Létrehozva a trezor profilja", + "saved": "A trezor profilja mentve", + "deleted": "Tárolóprofil törölve", + "saveFailed": "Nem sikerült menteni a Vault profilját", + "deleteFailed": "Nem sikerült törölni a Vault profilját", + "save": "Profil mentése", + "validationError": "Név, trezor címe és SSH aláírói szerepkör megadása kötelező.", + "none": "Még nincsenek Vault-profilok.", + "sharedBadge": "megosztott" + }, + "dialog": { + "title": "Bejelentkezés szükséges a széfbe", + "description": "Megnyílt egy ablak a HashiCorp Vaultba való bejelentkezéshez. Fejezze be a bejelentkezést ott; a kapcsolat automatikusan folytatódik.", + "failed": "A trezor hitelesítése sikertelen. Próbálja újra.", + "reopen": "Bejelentkezési ablak újranyitása" + } +} diff --git a/plugins/vault/locales/translated/id_ID.json b/plugins/vault/locales/translated/id_ID.json new file mode 100644 index 000000000..275a27e0d --- /dev/null +++ b/plugins/vault/locales/translated/id_ID.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Profil Penandatangan Vault", + "selectProfile": "Pilih profil Vault...", + "hint": "Pengaturan berasal dari profil bersama; Anda akan masuk ke Vault melalui OIDC saat terhubung. Tidak ada rahasia yang disimpan.", + "docs": "Lihat dokumen" + }, + "profiles": { + "new": "Profil baru", + "manage": "Kelola profil Vault", + "name": "Nama yang Ramah", + "vaultAddr": "Alamat Brankas", + "namespace": "Ruang nama", + "oidcMount": "OIDC Otorisasi Pemasangan", + "oidcRole": "Peran OIDC", + "sshMount": "Pemasangan Rahasia SSH", + "sshRole": "Peran Penandatangan SSH", + "validPrincipals": "Prinsip-prinsip yang Sah", + "keyType": "Jenis Kunci Sementara", + "shareWithAll": "Bagikan dengan semua pengguna", + "create": "Buat profil", + "created": "Profil Vault telah dibuat.", + "saved": "Profil Vault tersimpan.", + "deleted": "Profil Vault dihapus", + "saveFailed": "Gagal menyimpan profil Vault", + "deleteFailed": "Gagal menghapus profil Vault", + "save": "Simpan profil", + "validationError": "Nama, alamat Vault, dan peran penandatangan SSH wajib diisi.", + "none": "Belum ada profil Vault.", + "sharedBadge": "dibagikan" + }, + "dialog": { + "title": "Login ke Vault diperlukan.", + "description": "Jendela telah terbuka untuk masuk ke HashiCorp Vault. Selesaikan proses masuk di sana; koneksi ini akan berlanjut secara otomatis.", + "failed": "Autentikasi Vault gagal. Silakan coba lagi.", + "reopen": "Buka kembali jendela masuk" + } +} diff --git a/plugins/vault/locales/translated/it_IT.json b/plugins/vault/locales/translated/it_IT.json new file mode 100644 index 000000000..b1a5011e2 --- /dev/null +++ b/plugins/vault/locales/translated/it_IT.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Profilo firmatario Vault", + "selectProfile": "Seleziona un profilo Vault...", + "hint": "Le impostazioni provengono dal profilo condiviso; effettuerai l'accesso a Vault tramite OIDC quando ti connetti. Nessun segreto è memorizzato.", + "docs": "Visualizza documentazione" + }, + "profiles": { + "new": "Nuovo profilo", + "manage": "Gestisci profili Vault", + "name": "Nome descrittivo", + "vaultAddr": "Indirizzo Vault", + "namespace": "Namespace", + "oidcMount": "Mount di autenticazione OIDC", + "oidcRole": "Ruolo OIDC", + "sshMount": "Mount dei segreti SSH", + "sshRole": "Ruolo firmatario SSH", + "validPrincipals": "Principal validi", + "keyType": "Tipo di chiave effimera", + "shareWithAll": "Condividi con tutti gli utenti", + "create": "Crea profilo", + "created": "Profilo Vault creato", + "saved": "Profilo Vault salvato", + "deleted": "Profilo Vault eliminato", + "saveFailed": "Salvataggio profilo Vault non riuscito", + "deleteFailed": "Eliminazione profilo Vault non riuscita", + "save": "Salva profilo", + "validationError": "Nome, indirizzo Vault e ruolo di firma SSH sono obbligatori", + "none": "Nessun profilo Vault ancora.", + "sharedBadge": "condiviso" + }, + "dialog": { + "title": "Accesso a Vault richiesto", + "description": "Una finestra si è aperta per accedere a HashiCorp Vault. Completa l'accesso lì; la connessione continuerà automaticamente.", + "failed": "Autenticazione Vault fallita. Riprova.", + "reopen": "Riapri la finestra di accesso" + } +} diff --git a/plugins/vault/locales/translated/ja_JP.json b/plugins/vault/locales/translated/ja_JP.json new file mode 100644 index 000000000..207ea9c56 --- /dev/null +++ b/plugins/vault/locales/translated/ja_JP.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Vault 署名者プロファイル", + "selectProfile": "Vault プロファイルを選択...", + "hint": "設定は共有プロファイルから取得され、接続時に OIDC で Vault にサインインします。シークレットは保存されません。", + "docs": "ドキュメントを表示" + }, + "profiles": { + "new": "新規プロファイル", + "manage": "Vault プロファイルの管理", + "name": "表示名", + "vaultAddr": "Vault アドレス", + "namespace": "名前空間", + "oidcMount": "OIDC 認証マウント", + "oidcRole": "OIDC ロール", + "sshMount": "SSH シークレットマウント", + "sshRole": "SSH 署名者ロール", + "validPrincipals": "有効なプリンシパル", + "keyType": "エフェメラルキータイプ", + "shareWithAll": "すべてのユーザーと共有", + "create": "プロファイルを作成", + "created": "Vaultプロファイルが作成されました", + "saved": "Vaultプロファイルが保存されました", + "deleted": "Vaultプロファイルが削除されました", + "saveFailed": "Vaultプロファイルの保存に失敗しました", + "deleteFailed": "Vaultプロファイルの削除に失敗しました", + "save": "プロファイルを保存", + "validationError": "名前、Vaultアドレス、SSH署名ロールは必須です", + "none": "まだVaultプロファイルがありません。", + "sharedBadge": "共有" + }, + "dialog": { + "title": "Vault サインインが必要です", + "description": "HashiCorp Vaultへのサインイン用のウィンドウが開きました。そこでサインインを完了すると、この接続は自動的に続行されます。", + "failed": "Vault 認証に失敗しました。もう一度お試しください。", + "reopen": "サインインウィンドウを再表示" + } +} diff --git a/plugins/vault/locales/translated/ko_KR.json b/plugins/vault/locales/translated/ko_KR.json new file mode 100644 index 000000000..6c849507f --- /dev/null +++ b/plugins/vault/locales/translated/ko_KR.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Vault 서명자 프로필", + "selectProfile": "Vault 프로필 선택...", + "hint": "설정은 공유 프로필에서 가져옵니다. 연결 시 OIDC를 통해 Vault에 로그인하게 됩니다. 비밀은 저장되지 않습니다.", + "docs": "문서 보기" + }, + "profiles": { + "new": "새 프로필", + "manage": "Vault 프로필 관리", + "name": "별칭", + "vaultAddr": "Vault 주소", + "namespace": "네임스페이스", + "oidcMount": "OIDC 인증 마운트", + "oidcRole": "OIDC 역할", + "sshMount": "SSH 시크릿 마운트", + "sshRole": "SSH 서명자 역할", + "validPrincipals": "유효한 프린시펄", + "keyType": "임시 키 유형", + "shareWithAll": "모든 사용자와 공유", + "create": "프로필 생성", + "created": "Vault 프로필 생성됨", + "saved": "Vault 프로필 저장됨", + "deleted": "Vault 프로필 삭제됨", + "saveFailed": "Vault 프로필 저장 실패", + "deleteFailed": "Vault 프로필 삭제 실패", + "save": "프로필 저장", + "validationError": "이름, Vault 주소 및 SSH 서명자 역할이 필요합니다.", + "none": "아직 Vault 프로필이 없습니다.", + "sharedBadge": "공유" + }, + "dialog": { + "title": "Vault 로그인 필요", + "description": "HashiCorp Vault 로그인을 위한 창이 열렸습니다. 해당 창에서 로그인을 완료하면 연결이 자동으로 계속됩니다.", + "failed": "Vault 인증에 실패했습니다. 다시 시도하세요.", + "reopen": "로그인 창 다시 열기" + } +} diff --git a/plugins/vault/locales/translated/nl_NL.json b/plugins/vault/locales/translated/nl_NL.json new file mode 100644 index 000000000..640a34b67 --- /dev/null +++ b/plugins/vault/locales/translated/nl_NL.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Vault-ondertekenaarprofiel", + "selectProfile": "Selecteer een Vault-profiel...", + "hint": "De instellingen zijn afkomstig van het gedeelde profiel; u meldt zich bij Vault aan via OIDC wanneer u verbinding maakt. Er worden geen geheimen opgeslagen.", + "docs": "Documenten bekijken" + }, + "profiles": { + "new": "Nieuw profiel", + "manage": "Beheer Vault-profielen", + "name": "Vriendelijke naam", + "vaultAddr": "Kluisadres", + "namespace": "Naamruimte", + "oidcMount": "OIDC-authenticatiekoppeling", + "oidcRole": "OIDC-rol", + "sshMount": "SSH-geheimen Mount", + "sshRole": "SSH-ondertekenaarrol", + "validPrincipals": "Geldige principes", + "keyType": "Tijdelijk sleuteltype", + "shareWithAll": "Delen met alle gebruikers", + "create": "Profiel aanmaken", + "created": "Vault-profiel aangemaakt", + "saved": "Vault-profiel opgeslagen", + "deleted": "Kluisprofiel verwijderd", + "saveFailed": "Het opslaan van het Vault-profiel is mislukt.", + "deleteFailed": "Het verwijderen van het Vault-profiel is mislukt.", + "save": "Profiel opslaan", + "validationError": "Naam, kluisadres en SSH-ondertekeningsrol zijn vereist.", + "none": "Nog geen Vault-profielen beschikbaar.", + "sharedBadge": "gedeeld" + }, + "dialog": { + "title": "Aanmelden bij Vault is vereist.", + "description": "Er is een venster geopend om in te loggen bij HashiCorp Vault. Voltooi de aanmelding daar; deze verbinding wordt automatisch tot stand gebracht.", + "failed": "Authenticatie voor de kluis is mislukt. Probeer het opnieuw.", + "reopen": "Heropen het aanmeldvenster" + } +} diff --git a/plugins/vault/locales/translated/no_NO.json b/plugins/vault/locales/translated/no_NO.json new file mode 100644 index 000000000..0e2bd92bf --- /dev/null +++ b/plugins/vault/locales/translated/no_NO.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Profil for hvelvsigner", + "selectProfile": "Velg en Vault-profil...", + "hint": "Innstillingene kommer fra den delte profilen. Du logger deg på Vault via OIDC når du kobler til. Ingen hemmeligheter lagres.", + "docs": "Vis dokumenter" + }, + "profiles": { + "new": "Ny profil", + "manage": "Administrer Vault-profiler", + "name": "Vennlig navn", + "vaultAddr": "Hvelvadresse", + "namespace": "Navnerom", + "oidcMount": "OIDC-autentiseringsmontering", + "oidcRole": "OIDC-rolle", + "sshMount": "SSH-hemmeligheter monteres", + "sshRole": "SSH-signatørrolle", + "validPrincipals": "Gyldige prinsipaler", + "keyType": "Kortvarig nøkkeltype", + "shareWithAll": "Del med alle brukere", + "create": "Opprett profil", + "created": "Vault-profil opprettet", + "saved": "Vault-profilen er lagret", + "deleted": "Vault-profilen er slettet", + "saveFailed": "Kunne ikke lagre Vault-profilen", + "deleteFailed": "Kunne ikke slette Vault-profilen", + "save": "Lagre profil", + "validationError": "Navn, hvelvadresse og SSH-signerrolle er obligatorisk", + "none": "Ingen Vault-profiler ennå.", + "sharedBadge": "delt" + }, + "dialog": { + "title": "Pålogging til arkivet kreves", + "description": "Et vindu har åpnet seg for å logge på HashiCorp Vault. Fullfør påloggingen der; denne tilkoblingen vil fortsette automatisk.", + "failed": "Hvelv-autentisering mislyktes. Prøv på nytt.", + "reopen": "Åpne påloggingsvinduet på nytt" + } +} diff --git a/plugins/vault/locales/translated/pl_PL.json b/plugins/vault/locales/translated/pl_PL.json new file mode 100644 index 000000000..5cbcc5ae8 --- /dev/null +++ b/plugins/vault/locales/translated/pl_PL.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Profil podpisujący Vault", + "selectProfile": "Wybierz profil Vault...", + "hint": "Ustawienia pochodzą z profilu współdzielonego. Po połączeniu zalogujesz się do Vault przez OIDC. Nie są przechowywane żadne sekrety.", + "docs": "Zobacz dokumentację" + }, + "profiles": { + "new": "Nowy profil", + "manage": "Zarządzaj profilami Vault", + "name": "Przyjazna nazwa", + "vaultAddr": "Adres Vault", + "namespace": "Przestrzeń nazw", + "oidcMount": "Mount uwierzytelniania OIDC", + "oidcRole": "Rola OIDC", + "sshMount": "Mount sekretów SSH", + "sshRole": "Rola podpisująca SSH", + "validPrincipals": "Dozwoleni użytkownicy (principals)", + "keyType": "Typ klucza efemerycznego", + "shareWithAll": "Udostępnij wszystkim użytkownikom", + "create": "Utwórz profil", + "created": "Utworzono profil Vault", + "saved": "Zapisano profil Vault", + "deleted": "Usunięto profil Vault", + "saveFailed": "Nie udało się zapisać profilu Vault", + "deleteFailed": "Nie udało się usunąć profilu Vault", + "save": "Zapisz profil", + "validationError": "Wymagane są nazwa, adres Vault i rola podpisująca SSH", + "none": "Brak profili Vault.", + "sharedBadge": "współdzielony" + }, + "dialog": { + "title": "Wymagane logowanie do Vault", + "description": "Otworzyło się okno logowania do HashiCorp Vault. Dokończ logowanie, a połączenie zostanie nawiązane automatycznie.", + "failed": "Uwierzytelnienie w Vault nie powiodło się. Spróbuj ponownie.", + "reopen": "Otwórz ponownie okno logowania" + } +} diff --git a/plugins/vault/locales/translated/pt_BR.json b/plugins/vault/locales/translated/pt_BR.json new file mode 100644 index 000000000..c808033b4 --- /dev/null +++ b/plugins/vault/locales/translated/pt_BR.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Perfil do assinante do Vault", + "selectProfile": "Selecione um perfil do Vault...", + "hint": "As configurações são provenientes do perfil compartilhado; você fará login no Vault via OIDC ao se conectar. Nenhum segredo é armazenado.", + "docs": "Ver documentos" + }, + "profiles": { + "new": "Novo perfil", + "manage": "Gerenciar perfis do Vault", + "name": "Nome amigável", + "vaultAddr": "Endereço do cofre", + "namespace": "Espaço de nomes", + "oidcMount": "Montagem de autenticação OIDC", + "oidcRole": "Função OIDC", + "sshMount": "Montagem de segredos SSH", + "sshRole": "Função de assinatura SSH", + "validPrincipals": "Principais válidos", + "keyType": "Tipo de chave efêmera", + "shareWithAll": "Compartilhe com todos os usuários", + "create": "Criar perfil", + "created": "Perfil do Vault criado", + "saved": "Perfil do Vault salvo", + "deleted": "Perfil do Vault excluído", + "saveFailed": "Falha ao salvar o perfil do Vault", + "deleteFailed": "Falha ao excluir o perfil do Vault", + "save": "Salvar perfil", + "validationError": "Nome, endereço do Vault e função de assinatura SSH são obrigatórios.", + "none": "Ainda não há perfis no Vault.", + "sharedBadge": "compartilhado" + }, + "dialog": { + "title": "É necessário fazer login no cofre.", + "description": "Uma janela foi aberta para fazer login no HashiCorp Vault. Conclua o login; esta conexão será estabelecida automaticamente.", + "failed": "A autenticação do cofre falhou. Tente novamente.", + "reopen": "Reabrir janela de login" + } +} diff --git a/plugins/vault/locales/translated/pt_PT.json b/plugins/vault/locales/translated/pt_PT.json new file mode 100644 index 000000000..5c3ce8cf9 --- /dev/null +++ b/plugins/vault/locales/translated/pt_PT.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Perfil de assinatura Vault", + "selectProfile": "Selecionar um perfil Vault...", + "hint": "As definições provêm do perfil partilhado; ao ligar, iniciará sessão no Vault via OIDC. Nenhum segredo é armazenado.", + "docs": "Ver documentação" + }, + "profiles": { + "new": "Novo perfil", + "manage": "Gerir perfis Vault", + "name": "Nome Amigável", + "vaultAddr": "Endereço Vault", + "namespace": "Namespace", + "oidcMount": "Mount de autenticação OIDC", + "oidcRole": "Função OIDC", + "sshMount": "Mount de segredos SSH", + "sshRole": "Função de assinatura SSH", + "validPrincipals": "Principais válidos", + "keyType": "Tipo de chave efémera", + "shareWithAll": "Partilhar com todos os utilizadores", + "create": "Criar perfil", + "created": "Perfil Vault criado", + "saved": "Perfil Vault guardado", + "deleted": "Perfil Vault eliminado", + "saveFailed": "Falha ao guardar perfil Vault", + "deleteFailed": "Falha ao eliminar perfil Vault", + "save": "Guardar perfil", + "validationError": "Nome, endereço Vault e função de signatário SSH são obrigatórios", + "none": "Ainda sem perfis Vault.", + "sharedBadge": "partilhado" + }, + "dialog": { + "title": "Início de sessão no Vault necessário", + "description": "Foi aberta uma janela para iniciar sessão no HashiCorp Vault. Conclua o início de sessão aí; esta ligação continuará automaticamente.", + "failed": "A autenticação no Vault falhou. Tente novamente.", + "reopen": "Reabrir janela de início de sessão" + } +} diff --git a/plugins/vault/locales/translated/ro_RO.json b/plugins/vault/locales/translated/ro_RO.json new file mode 100644 index 000000000..a877c44a2 --- /dev/null +++ b/plugins/vault/locales/translated/ro_RO.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Profilul semnatarului din seif", + "selectProfile": "Selectați un profil Vault...", + "hint": "Setările provin din profilul partajat; te vei conecta la Vault prin OIDC când te conectezi. Nu sunt stocate secrete.", + "docs": "Vizualizați documentele" + }, + "profiles": { + "new": "Profil nou", + "manage": "Gestionați profilurile din seif", + "name": "Nume prietenos", + "vaultAddr": "Adresă seif", + "namespace": "Spațiu de nume", + "oidcMount": "Montare autentificare OIDC", + "oidcRole": "Rolul OIDC", + "sshMount": "Secretele SSH Mount", + "sshRole": "Rolul semnatarului SSH", + "validPrincipals": "Principali validi", + "keyType": "Tip de cheie efemeră", + "shareWithAll": "Partajați cu toți utilizatorii", + "create": "Creați un profil", + "created": "Profilul seifului a fost creat", + "saved": "Profilul seifului a fost salvat", + "deleted": "Profilul seifului a fost șters", + "saveFailed": "Nu s-a putut salva profilul din Seif", + "deleteFailed": "Nu s-a putut șterge profilul Vault", + "save": "Salvați profilul", + "validationError": "Numele, adresa seifului și rolul semnatarului SSH sunt obligatorii.", + "none": "Încă nu există profiluri Vault.", + "sharedBadge": "partajat" + }, + "dialog": { + "title": "Este necesară conectarea la seif", + "description": "S-a deschis o fereastră pentru a vă conecta la HashiCorp Vault. Finalizați conectarea acolo; această conexiune va continua automat.", + "failed": "Autentificarea în seif a eșuat. Vă rugăm să încercați din nou.", + "reopen": "Redeschideți fereastra de conectare" + } +} diff --git a/plugins/vault/locales/translated/ru_RU.json b/plugins/vault/locales/translated/ru_RU.json new file mode 100644 index 000000000..58e482221 --- /dev/null +++ b/plugins/vault/locales/translated/ru_RU.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Профиль подписчика Vault", + "selectProfile": "Выберите профиль Vault...", + "hint": "Настройки берутся из общего профиля; при подключении вы войдёте в Vault через OIDC. Секреты не хранятся.", + "docs": "Документация" + }, + "profiles": { + "new": "Новый профиль", + "manage": "Управление профилями Vault", + "name": "Дружественное имя", + "vaultAddr": "Адрес Vault", + "namespace": "Пространство имён", + "oidcMount": "Монтирование аутентификации OIDC", + "oidcRole": "Роль OIDC", + "sshMount": "Монтирование секретов SSH", + "sshRole": "Роль подписчика SSH", + "validPrincipals": "Допустимые принципалы", + "keyType": "Тип эфемерного ключа", + "shareWithAll": "Общий доступ для всех пользователей", + "create": "Создать профиль", + "created": "Профиль Vault создан", + "saved": "Профиль Vault сохранён", + "deleted": "Профиль Vault удалён", + "saveFailed": "Не удалось сохранить профиль Vault", + "deleteFailed": "Не удалось удалить профиль Vault", + "save": "Сохранить профиль", + "validationError": "Требуются имя, адрес Vault и роль SSH подписанта", + "none": "Пока нет профилей Vault.", + "sharedBadge": "общий" + }, + "dialog": { + "title": "Требуется вход в Vault", + "description": "Открыто окно для входа в HashiCorp Vault. Завершите вход там; подключение продолжится автоматически.", + "failed": "Аутентификация Vault не удалась. Попробуйте снова.", + "reopen": "Повторно открыть окно входа" + } +} diff --git a/plugins/vault/locales/translated/sr_SP.json b/plugins/vault/locales/translated/sr_SP.json new file mode 100644 index 000000000..cff081c12 --- /dev/null +++ b/plugins/vault/locales/translated/sr_SP.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Профил потписника трезора", + "selectProfile": "Изаберите профил трезора...", + "hint": "Подешавања долазе из дељеног профила; пријавићете се у Vault преко OIDC-а када се повежете. Ниједне тајне се не чувају.", + "docs": "Прикажи документе" + }, + "profiles": { + "new": "Нови профил", + "manage": "Управљање профилима трезора", + "name": "Пријатељско име", + "vaultAddr": "Адреса трезора", + "namespace": "Именски простор", + "oidcMount": "OIDC аутентификација монтирања", + "oidcRole": "Улога OIDC-а", + "sshMount": "SSH тајне носач", + "sshRole": "Улога SSH потписника", + "validPrincipals": "Важећи принципали", + "keyType": "Тип ефемерног кључа", + "shareWithAll": "Подели са свим корисницима", + "create": "Направи профил", + "created": "Профил трезора је креиран", + "saved": "Профил трезора је сачуван", + "deleted": "Профил трезора је обрисан", + "saveFailed": "Чување профила у трезору није успело", + "deleteFailed": "Брисање профила у Трезору није успело", + "save": "Сачувај профил", + "validationError": "Име, адреса трезора и улога SSH потписника су обавезни", + "none": "Још нема профила у Трезору.", + "sharedBadge": "дељено" + }, + "dialog": { + "title": "Потребно је пријављивање у трезор", + "description": "Отворио се прозор за пријављивање на HashiCorp Vault. Завршите пријављивање тамо; ова веза ће се аутоматски наставити.", + "failed": "Аутентификација трезора није успела. Молимо покушајте поново.", + "reopen": "Поново отвори прозор за пријављивање" + } +} diff --git a/plugins/vault/locales/translated/sv_SE.json b/plugins/vault/locales/translated/sv_SE.json new file mode 100644 index 000000000..cd1af3e77 --- /dev/null +++ b/plugins/vault/locales/translated/sv_SE.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Profil för valvsignerare", + "selectProfile": "Välj en Vault-profil...", + "hint": "Inställningarna kommer från den delade profilen; du loggar in på Vault via OIDC när du ansluter. Inga hemligheter lagras.", + "docs": "Visa dokument" + }, + "profiles": { + "new": "Ny profil", + "manage": "Hantera Vault-profiler", + "name": "Vänligt namn", + "vaultAddr": "Valvadress", + "namespace": "Namnrymd", + "oidcMount": "OIDC-autentiseringsmontering", + "oidcRole": "OIDC-roll", + "sshMount": "SSH-hemligheter monteras", + "sshRole": "SSH-signerarroll", + "validPrincipals": "Giltiga principer", + "keyType": "Tillfällig nyckeltyp", + "shareWithAll": "Dela med alla användare", + "create": "Skapa profil", + "created": "Arkivprofil skapad", + "saved": "Arkivprofilen har sparats", + "deleted": "Arkivprofil raderad", + "saveFailed": "Kunde inte spara arkivprofilen", + "deleteFailed": "Misslyckades med att ta bort Arkiv-profilen", + "save": "Spara profil", + "validationError": "Namn, valvadress och SSH-signerarroll krävs", + "none": "Inga Vault-profiler ännu.", + "sharedBadge": "delad" + }, + "dialog": { + "title": "Inloggning till arkivet krävs", + "description": "Ett fönster har öppnats för att logga in på HashiCorp Vault. Slutför inloggningen där; anslutningen fortsätter automatiskt.", + "failed": "Valvautentisering misslyckades. Försök igen.", + "reopen": "Öppna inloggningsfönstret igen" + } +} diff --git a/plugins/vault/locales/translated/th_TH.json b/plugins/vault/locales/translated/th_TH.json new file mode 100644 index 000000000..9690ca65e --- /dev/null +++ b/plugins/vault/locales/translated/th_TH.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "โปรไฟล์ผู้ลงนาม Vault", + "selectProfile": "เลือกโปรไฟล์ Vault...", + "hint": "การตั้งค่าต่างๆ มาจากโปรไฟล์ที่ใช้ร่วมกัน คุณจะเข้าสู่ระบบ Vault ผ่าน OIDC เมื่อเชื่อมต่อ ไม่มีข้อมูลลับใดๆ ถูกจัดเก็บไว้", + "docs": "ดูเอกสาร" + }, + "profiles": { + "new": "โปรไฟล์ใหม่", + "manage": "จัดการโปรไฟล์ Vault", + "name": "ชื่อที่เป็นมิตร", + "vaultAddr": "ที่อยู่ห้องนิรภัย", + "namespace": "เนมสเปซ", + "oidcMount": "OIDC Auth Mount", + "oidcRole": "บทบาทของ OIDC", + "sshMount": "SSH Secrets Mount", + "sshRole": "บทบาทผู้ลงนาม SSH", + "validPrincipals": "หลักการที่ถูกต้อง", + "keyType": "ประเภทคีย์ชั่วคราว", + "shareWithAll": "แชร์กับผู้ใช้ทั้งหมด", + "create": "สร้างโปรไฟล์", + "created": "สร้างโปรไฟล์ Vault แล้ว", + "saved": "บันทึกโปรไฟล์ Vault แล้ว", + "deleted": "โปรไฟล์ Vault ถูกลบแล้ว", + "saveFailed": "ไม่สามารถบันทึกโปรไฟล์ Vault ได้", + "deleteFailed": "ไม่สามารถลบโปรไฟล์ Vault ได้", + "save": "บันทึกโปรไฟล์", + "validationError": "จำเป็นต้องระบุชื่อ ที่อยู่ Vault และบทบาทผู้ลงนาม SSH", + "none": "ยังไม่มีโปรไฟล์ Vault ใดๆ", + "sharedBadge": "แชร์" + }, + "dialog": { + "title": "ต้องลงชื่อเข้าใช้ Vault", + "description": "หน้าต่างสำหรับเข้าสู่ระบบ HashiCorp Vault ได้เปิดขึ้นแล้ว โปรดทำการเข้าสู่ระบบให้เสร็จสมบูรณ์ การเชื่อมต่อนี้จะดำเนินการต่อโดยอัตโนมัติ", + "failed": "การตรวจสอบสิทธิ์ Vault ล้มเหลว โปรดลองอีกครั้ง", + "reopen": "เปิดหน้าต่างเข้าสู่ระบบอีกครั้ง" + } +} diff --git a/plugins/vault/locales/translated/tr_TR.json b/plugins/vault/locales/translated/tr_TR.json new file mode 100644 index 000000000..3aaaad451 --- /dev/null +++ b/plugins/vault/locales/translated/tr_TR.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Vault İmzalayıcı Profili", + "selectProfile": "Bir Vault profili seçin...", + "hint": "Ayarlar paylaşılan profilden gelir; bağlandığınızda OIDC ile Vault'ta oturum açarsınız. Hiçbir gizli bilgi saklanmaz.", + "docs": "Dokümanları görüntüle" + }, + "profiles": { + "new": "Yeni profil", + "manage": "Vault Profillerini Yönet", + "name": "Gösterim Adı", + "vaultAddr": "Vault Adresi", + "namespace": "Ad Alanı", + "oidcMount": "OIDC Kimlik Doğrulama Bağlama", + "oidcRole": "OIDC Rolü", + "sshMount": "SSH Gizli Dizin Bağlama", + "sshRole": "SSH İmzalayıcı Rolü", + "validPrincipals": "Geçerli Asıllar", + "keyType": "Geçici Anahtar Türü", + "shareWithAll": "Tüm Kullanıcılarla Paylaş", + "create": "Profil oluştur", + "created": "Vault profili oluşturuldu", + "saved": "Vault profili kaydedildi", + "deleted": "Vault profili silindi", + "saveFailed": "Vault profili kaydedilemedi", + "deleteFailed": "Vault profili silinemedi", + "save": "Profili kaydet", + "validationError": "Ad, Vault adresi ve SSH imzalama rolü gereklidir", + "none": "Henüz Vault profili yok.", + "sharedBadge": "paylaşılan" + }, + "dialog": { + "title": "Vault Oturum Açması Gerekli", + "description": "HashiCorp Vault'ta oturum açmak için bir pencere açıldı. Orada oturum açmayı tamamlayın; bu bağlantı otomatik olarak devam edecek.", + "failed": "Vault kimlik doğrulaması başarısız oldu. Lütfen tekrar deneyin.", + "reopen": "Oturum açma penceresini yeniden aç" + } +} diff --git a/plugins/vault/locales/translated/uk_UA.json b/plugins/vault/locales/translated/uk_UA.json new file mode 100644 index 000000000..d0b459d12 --- /dev/null +++ b/plugins/vault/locales/translated/uk_UA.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Профіль підписанта сховища", + "selectProfile": "Виберіть профіль Сховища...", + "hint": "Налаштування надходять зі спільного профілю; ви входите у Сейф через OIDC під час підключення. Секрети не зберігаються.", + "docs": "Переглянути документи" + }, + "profiles": { + "new": "Новий профіль", + "manage": "Керування профілями Сховища", + "name": "Зрозуміле ім'я", + "vaultAddr": "Адреса сховища", + "namespace": "Простір імен", + "oidcMount": "Монтування автентифікації OIDC", + "oidcRole": "Роль OIDC", + "sshMount": "SSH-секрети кріплення", + "sshRole": "Роль підписувача SSH", + "validPrincipals": "Дійсні принципали", + "keyType": "Тип тимчасового ключа", + "shareWithAll": "Поділитися з усіма користувачами", + "create": "Створити профіль", + "created": "Профіль сховища створено", + "saved": "Профіль сховища збережено", + "deleted": "Профіль сховища видалено", + "saveFailed": "Не вдалося зберегти профіль Сейфа", + "deleteFailed": "Не вдалося видалити профіль Сейфа", + "save": "Зберегти профіль", + "validationError": "Ім'я, адреса сховища та роль підписувача SSH обов'язкові", + "none": "Поки що немає профілів Сейфа.", + "sharedBadge": "спільний" + }, + "dialog": { + "title": "Потрібен вхід до Сейфа", + "description": "Відкрилося вікно для входу в HashiCorp Vault. Завершіть вхід там; це з’єднання буде продовжено автоматично.", + "failed": "Не вдалося виконати автентифікацію сховища. Спробуйте ще раз.", + "reopen": "Знову відкрити вікно входу" + } +} diff --git a/plugins/vault/locales/translated/vi_VN.json b/plugins/vault/locales/translated/vi_VN.json new file mode 100644 index 000000000..2e88e9727 --- /dev/null +++ b/plugins/vault/locales/translated/vi_VN.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Hồ sơ người ký Vault", + "selectProfile": "Chọn hồ sơ Vault...", + "hint": "Cài đặt từ hồ sơ dùng chung; bạn sẽ đăng nhập Vault qua OIDC khi kết nối. Không lưu bí mật.", + "docs": "Xem tài liệu" + }, + "profiles": { + "new": "Hồ sơ mới", + "manage": "Quản lý hồ sơ Vault", + "name": "Tên hiển thị", + "vaultAddr": "Địa chỉ Vault", + "namespace": "Không gian tên", + "oidcMount": "Điểm gắn xác thực OIDC", + "oidcRole": "Vai trò OIDC", + "sshMount": "Điểm gắn bí mật SSH", + "sshRole": "Vai trò ký SSH", + "validPrincipals": "Các danh tính hợp lệ", + "keyType": "Loại khóa tạm thời", + "shareWithAll": "Chia sẻ với tất cả người dùng", + "create": "Tạo hồ sơ", + "created": "Đã tạo hồ sơ Vault", + "saved": "Đã lưu hồ sơ Vault", + "deleted": "Đã xóa hồ sơ Vault", + "saveFailed": "Không thể lưu hồ sơ Vault", + "deleteFailed": "Không thể xóa hồ sơ Vault", + "save": "Lưu hồ sơ", + "validationError": "Tên, địa chỉ Vault và vai trò ký SSH là bắt buộc", + "none": "Chưa có hồ sơ Vault nào.", + "sharedBadge": "được chia sẻ" + }, + "dialog": { + "title": "Yêu cầu đăng nhập Vault", + "description": "Một cửa sổ đã mở để đăng nhập vào HashiCorp Vault. Hoàn tất đăng nhập ở đó; kết nối này sẽ tự động tiếp tục.", + "failed": "Xác thực Vault thất bại. Vui lòng thử lại.", + "reopen": "Mở lại cửa sổ đăng nhập" + } +} diff --git a/plugins/vault/locales/translated/zh_CN.json b/plugins/vault/locales/translated/zh_CN.json new file mode 100644 index 000000000..7ea03c665 --- /dev/null +++ b/plugins/vault/locales/translated/zh_CN.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Vault 签名者配置文件", + "selectProfile": "选择 Vault 配置文件...", + "hint": "设置来自共享配置文件;连接时将使用 OIDC 登录 Vault。不会存储任何机密。", + "docs": "查看文档" + }, + "profiles": { + "new": "新建配置文件", + "manage": "管理 Vault 配置文件", + "name": "友好名称", + "vaultAddr": "Vault 地址", + "namespace": "命名空间", + "oidcMount": "OIDC 认证挂载点", + "oidcRole": "OIDC 角色", + "sshMount": "SSH 机密挂载点", + "sshRole": "SSH 签名者角色", + "validPrincipals": "有效主体", + "keyType": "临时密钥类型", + "shareWithAll": "与所有用户共享", + "create": "创建配置文件", + "created": "Vault 配置文件已创建", + "saved": "Vault 配置文件已保存", + "deleted": "Vault 配置文件已删除", + "saveFailed": "保存 Vault 配置文件失败", + "deleteFailed": "删除 Vault 配置文件失败", + "save": "保存配置文件", + "validationError": "名称、Vault 地址和 SSH 签名者角色为必填项", + "none": "暂无 Vault 配置文件。", + "sharedBadge": "共享" + }, + "dialog": { + "title": "需要 Vault 登录", + "description": "已打开窗口以登录 HashiCorp Vault。在该处完成登录;此连接将自动继续。", + "failed": "Vault 认证失败,请重试。", + "reopen": "重新打开登录窗口" + } +} diff --git a/plugins/vault/locales/translated/zh_TW.json b/plugins/vault/locales/translated/zh_TW.json new file mode 100644 index 000000000..7f00c0170 --- /dev/null +++ b/plugins/vault/locales/translated/zh_TW.json @@ -0,0 +1,38 @@ +{ + "editor": { + "profile": "Vault 簽署者設定檔", + "selectProfile": "選取 Vault 設定檔...", + "hint": "設定來自共用設定檔;連線時您將透過 OIDC 登入 Vault。不會儲存任何機密。", + "docs": "檢視文件" + }, + "profiles": { + "new": "新增設定檔", + "manage": "管理 Vault 設定檔", + "name": "易記名稱", + "vaultAddr": "Vault 位址", + "namespace": "命名空間", + "oidcMount": "OIDC 驗證掛載", + "oidcRole": "OIDC 角色", + "sshMount": "SSH 機密掛載", + "sshRole": "SSH 簽署者角色", + "validPrincipals": "有效主體", + "keyType": "暫時金鑰類型", + "shareWithAll": "與所有使用者共用", + "create": "建立設定檔", + "created": "Vault 設定檔已建立", + "saved": "Vault 設定檔已儲存", + "deleted": "Vault 設定檔已刪除", + "saveFailed": "無法儲存 Vault 設定檔", + "deleteFailed": "無法刪除 Vault 設定檔", + "save": "儲存設定檔", + "validationError": "名稱、Vault 位址和 SSH 簽署者角色為必填", + "none": "尚無 Vault 設定檔。", + "sharedBadge": "已共用" + }, + "dialog": { + "title": "需要 Vault 登入", + "description": "已開啟視窗以登入 HashiCorp Vault。在該處完成登入;此連線將自動繼續。", + "failed": "Vault 驗證失敗。請再試一次。", + "reopen": "重新開啟登入視窗" + } +} diff --git a/plugins/vault/manifest.json b/plugins/vault/manifest.json new file mode 100644 index 000000000..20074bbab --- /dev/null +++ b/plugins/vault/manifest.json @@ -0,0 +1,72 @@ +{ + "id": "vault", + "name": "HashiCorp Vault", + "version": "1.0.0", + "description": "Sign in to hosts with short-lived SSH certificates from a HashiCorp Vault SSH secrets engine, after an OIDC sign-in to Vault in the browser.", + "author": { + "name": "Termix" + }, + "license": "MIT", + "repository": "https://github.com/Termix-SSH/Termix", + "category": "Access & Security", + "icon": "KeyRound", + "engine": { + "termix": ">=2.9.0", + "api": "1" + }, + "capabilities": [ + "db:own", + "secrets:own", + "network:outbound", + "network:serve", + "auth:provide", + "ui:surface" + ], + "contributes": { + "auth": { + "sshAuthTypes": ["vault"] + }, + "permissions": [ + { + "name": "use", + "titleKey": "permissions.use.title", + "descriptionKey": "permissions.use.description", + "defaultRoles": ["admin", "user"] + }, + { + "name": "share", + "titleKey": "permissions.share.title", + "descriptionKey": "permissions.share.description", + "defaultRoles": ["admin"] + } + ], + "settings": { + "admin": [ + { + "key": "redirectUri", + "type": "custom", + "component": "redirectUri" + }, + { + "key": "legacyCallback", + "type": "boolean", + "labelKey": "settings.legacyCallback.label", + "descriptionKey": "settings.legacyCallback.description", + "default": false + } + ], + "host": { + "fields": [ + { + "key": "profileId", + "type": "number", + "labelKey": "settings.host.profileId.label", + "hidden": true + } + ] + } + } + }, + "locales": "locales", + "platforms": ["linux", "win32", "darwin"] +} diff --git a/plugins/vault/migrations/mysql/0001_adopt_vault_tables.sql b/plugins/vault/migrations/mysql/0001_adopt_vault_tables.sql new file mode 100644 index 000000000..6aed086da --- /dev/null +++ b/plugins/vault/migrations/mysql/0001_adopt_vault_tables.sql @@ -0,0 +1,37 @@ +-- vault 0001: adopt_vault_tables +-- Generated by termix-plugin migrations. Review before committing. + +CREATE TABLE IF NOT EXISTS `vault_profiles` ( + `id` int AUTO_INCREMENT PRIMARY KEY, + `user_id` varchar(255) NOT NULL, + `name` text NOT NULL, + `description` text, + `folder` text, + `tags` text, + `vault_addr` text NOT NULL, + `vault_namespace` text, + `oidc_mount` text, + `oidc_role` text, + `ssh_mount` text, + `ssh_role` text NOT NULL, + `valid_principals` text, + `key_type` text, + `shared` boolean NOT NULL DEFAULT false, + `sync_id` varchar(255) UNIQUE, + `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 +); +RENAME TABLE `vault_profiles` TO `p_vault_profiles`; +CREATE TABLE IF NOT EXISTS `vault_tokens` ( + `id` int AUTO_INCREMENT PRIMARY KEY, + `user_id` varchar(255) NOT NULL, + `profile_id` int NOT NULL, + `ssh_cert` text NOT NULL, + `private_key` text NOT NULL, + `created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP), + `expires_at` varchar(255) NOT NULL, + `last_used` text, + FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +); +RENAME TABLE `vault_tokens` TO `p_vault_tokens`; diff --git a/plugins/vault/migrations/postgres/0001_adopt_vault_tables.sql b/plugins/vault/migrations/postgres/0001_adopt_vault_tables.sql new file mode 100644 index 000000000..7f47ab993 --- /dev/null +++ b/plugins/vault/migrations/postgres/0001_adopt_vault_tables.sql @@ -0,0 +1,38 @@ +-- vault 0001: adopt_vault_tables +-- Generated by termix-plugin migrations. Review before committing. + +CREATE TABLE IF NOT EXISTS "vault_profiles" ( + "id" serial PRIMARY KEY, + "user_id" varchar(255) NOT NULL, + "name" text NOT NULL, + "description" text, + "folder" text, + "tags" text, + "vault_addr" text NOT NULL, + "vault_namespace" text, + "oidc_mount" text, + "oidc_role" text, + "ssh_mount" text, + "ssh_role" text NOT NULL, + "valid_principals" text, + "key_type" text, + "shared" boolean NOT NULL DEFAULT false, + "sync_id" varchar(255) UNIQUE, + "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 +); +ALTER TABLE "vault_profiles" RENAME TO "p_vault_profiles"; +CREATE TABLE IF NOT EXISTS "vault_tokens" ( + "id" serial PRIMARY KEY, + "user_id" varchar(255) NOT NULL, + "profile_id" integer NOT NULL, + "ssh_cert" text NOT NULL, + "private_key" text NOT NULL, + "created_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP, + "expires_at" varchar(255) NOT NULL, + "last_used" text, + FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE +); +CREATE UNIQUE INDEX IF NOT EXISTS "idx_vault_tokens_user_profile" ON "vault_tokens" ("user_id", "profile_id"); +ALTER TABLE "vault_tokens" RENAME TO "p_vault_tokens"; diff --git a/plugins/vault/migrations/snapshot.json b/plugins/vault/migrations/snapshot.json new file mode 100644 index 000000000..7217340f9 --- /dev/null +++ b/plugins/vault/migrations/snapshot.json @@ -0,0 +1,127 @@ +{ + "version": 1, + "tables": { + "profiles": { + "columns": { + "id": { + "type": "id", + "primaryKey": true, + "notNull": true + }, + "userId": { + "type": "refUser", + "length": 255, + "notNull": true + }, + "name": { + "type": "text", + "notNull": true + }, + "description": { + "type": "text" + }, + "folder": { + "type": "text" + }, + "tags": { + "type": "text" + }, + "vaultAddr": { + "type": "text", + "notNull": true + }, + "vaultNamespace": { + "type": "text" + }, + "oidcMount": { + "type": "text" + }, + "oidcRole": { + "type": "text" + }, + "sshMount": { + "type": "text" + }, + "sshRole": { + "type": "text", + "notNull": true + }, + "validPrincipals": { + "type": "text" + }, + "keyType": { + "type": "text" + }, + "shared": { + "type": "boolean", + "notNull": true, + "defaultValue": false + }, + "syncId": { + "type": "varchar", + "length": 255, + "unique": true + }, + "createdAt": { + "type": "text", + "notNull": true, + "defaultNow": true + }, + "updatedAt": { + "type": "text", + "notNull": true, + "defaultNow": true + } + }, + "indexes": [], + "adopts": "vault_profiles" + }, + "tokens": { + "columns": { + "id": { + "type": "id", + "primaryKey": true, + "notNull": true + }, + "userId": { + "type": "refUser", + "length": 255, + "notNull": true + }, + "profileId": { + "type": "integer", + "notNull": true + }, + "sshCert": { + "type": "text", + "notNull": true + }, + "privateKey": { + "type": "text", + "notNull": true + }, + "createdAt": { + "type": "text", + "notNull": true, + "defaultNow": true + }, + "expiresAt": { + "type": "varchar", + "length": 255, + "notNull": true + }, + "lastUsed": { + "type": "text" + } + }, + "indexes": [ + { + "name": "idx_vault_tokens_user_profile", + "columns": ["userId", "profileId"], + "unique": true + } + ], + "adopts": "vault_tokens" + } + } +} diff --git a/plugins/vault/migrations/sqlite/0001_adopt_vault_tables.sql b/plugins/vault/migrations/sqlite/0001_adopt_vault_tables.sql new file mode 100644 index 000000000..b1925731c --- /dev/null +++ b/plugins/vault/migrations/sqlite/0001_adopt_vault_tables.sql @@ -0,0 +1,38 @@ +-- vault 0001: adopt_vault_tables +-- Generated by termix-plugin migrations. Review before committing. + +CREATE TABLE IF NOT EXISTS "vault_profiles" ( + "id" integer PRIMARY KEY AUTOINCREMENT, + "user_id" text NOT NULL, + "name" text NOT NULL, + "description" text, + "folder" text, + "tags" text, + "vault_addr" text NOT NULL, + "vault_namespace" text, + "oidc_mount" text, + "oidc_role" text, + "ssh_mount" text, + "ssh_role" text NOT NULL, + "valid_principals" text, + "key_type" text, + "shared" integer NOT NULL DEFAULT 0, + "sync_id" text UNIQUE, + "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 +); +ALTER TABLE "vault_profiles" RENAME TO "p_vault_profiles"; +CREATE TABLE IF NOT EXISTS "vault_tokens" ( + "id" integer PRIMARY KEY AUTOINCREMENT, + "user_id" text NOT NULL, + "profile_id" integer NOT NULL, + "ssh_cert" text NOT NULL, + "private_key" text NOT NULL, + "created_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP, + "expires_at" text NOT NULL, + "last_used" text, + FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE +); +CREATE UNIQUE INDEX IF NOT EXISTS "idx_vault_tokens_user_profile" ON "vault_tokens" ("user_id", "profile_id"); +ALTER TABLE "vault_tokens" RENAME TO "p_vault_tokens"; diff --git a/plugins/vault/package.json b/plugins/vault/package.json new file mode 100644 index 000000000..0ed0bb5e2 --- /dev/null +++ b/plugins/vault/package.json @@ -0,0 +1,15 @@ +{ + "name": "@termix-plugin/vault", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "build": "termix-plugin build", + "test": "termix-plugin test", + "typecheck": "tsc -p tsconfig.json", + "validate": "termix-plugin validate" + }, + "devDependencies": { + "@termix/plugin-sdk": "*" + } +} diff --git a/plugins/vault/src/backend/auth-session.ts b/plugins/vault/src/backend/auth-session.ts new file mode 100644 index 000000000..3c7905062 --- /dev/null +++ b/plugins/vault/src/backend/auth-session.ts @@ -0,0 +1,206 @@ +import type { PluginContext } from "@termix/plugin-sdk/backend"; +import type { TokenStore } from "./token-store.js"; +import { + completeVaultOidc, + generateEphemeralKeyPair, + signWithVault, + startVaultOidc, + type VaultProfileConfig, +} from "./vault-client.js"; + +/** The redirect URI 2.8 had Vault roles allow. */ +export const LEGACY_CALLBACK_PATH = "/vault/oidc/callback"; +export const CALLBACK_PATH = "/plugin-api/vault/oidc/callback"; + +const AUTH_TIMEOUT_MS = 5 * 60 * 1000; + +/** The terminal socket, as far as a sign-in needs it. */ +export interface SignInSocket { + send: (data: string) => void; + on?: (event: "close", listener: () => void) => void; +} + +export interface StartRequest { + userId: string; + hostId: number; + profile: VaultProfileConfig; + socket: SignInSocket; + requestOrigin: string; +} + +export interface CallbackResult { + ok: boolean; + message: string; +} + +interface Session { + state: string; + userId: string; + hostId: number; + profile: VaultProfileConfig; + clientNonce: string; + privateKey: string; + publicKey: string; + socket: SignInSocket; + timeout: ReturnType; +} + +function send(socket: SignInSocket, message: Record): void { + try { + socket.send(JSON.stringify(message)); + } catch { + // socket already gone + } +} + +function errorMessage(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} + +export type AuthSessions = ReturnType; + +/** + * Vault OIDC sign-ins in flight, keyed by the state Vault put in its auth + * URL so the unauthenticated browser callback finds its way back. Created + * per activation; closeAll runs on deactivate. + */ +export function createAuthSessions(ctx: PluginContext, tokens: TokenStore) { + const sessions = new Map(); + + function end(session: Session): void { + clearTimeout(session.timeout); + sessions.delete(session.state); + } + + async function callbackPath(): Promise { + const legacy = await ctx.settings.get("legacyCallback"); + return legacy ? LEGACY_CALLBACK_PATH : CALLBACK_PATH; + } + + return { + /** Asks Vault for an auth URL and sends it to the terminal. */ + async start(request: StartRequest): Promise { + try { + const keyPair = generateEphemeralKeyPair(request.profile.keyType); + const redirectUri = `${request.requestOrigin}${await callbackPath()}`; + const { authUrl, state, clientNonce } = await startVaultOidc( + ctx.fetch, + request.profile, + redirectUri, + ); + + const existing = sessions.get(state); + if (existing) end(existing); + + const session: Session = { + state, + userId: request.userId, + hostId: request.hostId, + profile: request.profile, + clientNonce, + privateKey: keyPair.privateKey, + publicKey: keyPair.publicKey, + socket: request.socket, + timeout: setTimeout(() => { + const current = sessions.get(state); + if (current) end(current); + }, AUTH_TIMEOUT_MS), + }; + sessions.set(state, session); + request.socket.on?.("close", () => { + const current = sessions.get(state); + if (current) end(current); + }); + + send(request.socket, { + type: "vault_auth_url", + hostId: request.hostId, + requestId: state, + url: authUrl, + }); + } catch (error) { + const message = errorMessage(error); + ctx.log.error(`Failed to start Vault authentication: ${message}`); + send(request.socket, { + type: "vault_error", + hostId: request.hostId, + error: message, + }); + } + }, + + /** + * The identity provider's redirect, via Vault: exchanges the code for a + * Vault token, has Vault sign the ephemeral key and caches the result. + */ + async complete(state: string, code: string): Promise { + const session = sessions.get(state); + if (!session) { + return { + ok: false, + message: "This sign-in request is no longer active.", + }; + } + end(session); + + try { + const token = await completeVaultOidc(ctx.fetch, session.profile, { + state, + code, + clientNonce: session.clientNonce, + }); + const signed = await signWithVault( + ctx.fetch, + session.profile, + token, + session.publicKey, + ); + const expiresAt = await tokens.save( + session.userId, + session.profile.id, + session.privateKey, + signed, + ); + ctx.log.info( + `Vault signed a certificate for host ${session.hostId} with profile ${session.profile.id}`, + ); + send(session.socket, { + type: "vault_completed", + hostId: session.hostId, + requestId: state, + expiresAt, + }); + return { + ok: true, + message: "You can close this window and return to Termix.", + }; + } catch (error) { + const message = errorMessage(error); + ctx.log.error(`Vault sign-in failed: ${message}`); + send(session.socket, { + type: "vault_error", + hostId: session.hostId, + requestId: state, + error: message, + }); + return { ok: false, message }; + } + }, + + /** Cancels by request id, or every sign-in the user has for a host. */ + cancel(userId: string, hostId?: number, requestId?: string): void { + for (const session of [...sessions.values()]) { + if (session.userId !== userId) continue; + if ( + requestId ? session.state === requestId : session.hostId === hostId + ) { + end(session); + } + } + }, + + closeAll(): void { + for (const session of [...sessions.values()]) end(session); + }, + }; +} diff --git a/plugins/vault/src/backend/index.ts b/plugins/vault/src/backend/index.ts new file mode 100644 index 000000000..af9b6d6b0 --- /dev/null +++ b/plugins/vault/src/backend/index.ts @@ -0,0 +1,41 @@ +import type { Router } from "express"; +import type { PluginContext } from "@termix/plugin-sdk/backend"; +import { profiles as profilesTable, tokens as tokensTable } from "./tables.js"; +import { createProfileStore, SYNC_ENTITY } from "./profile-store.js"; +import { createTokenStore } from "./token-store.js"; +import { createAuthSessions } from "./auth-session.js"; +import { createVaultProvider } from "./provider.js"; +import { PUBLIC_PATHS, registerRoutes } from "./routes.js"; + +export async function activate(ctx: PluginContext) { + const profilesDb = await ctx.db.define(profilesTable); + const tokensDb = await ctx.db.define(tokensTable); + const profiles = createProfileStore(ctx, profilesDb, tokensDb); + const tokens = createTokenStore(ctx, tokensDb); + + const sessions = createAuthSessions(ctx, tokens); + ctx.disposables.add(() => sessions.closeAll()); + + ctx.auth.registerSshAuthProvider( + createVaultProvider(ctx, profiles, tokens, sessions), + ); + + // Same wire name as 2.8, where core synced it. Hosts point at a profile + // through the profileId host setting, not a column, so nothing references it. + ctx.sync.registerEntity({ + type: SYNC_ENTITY, + table: profilesDb, + order: 20, + }); + + registerRoutes( + ctx, + ctx.http.router({ public: PUBLIC_PATHS }), + profiles, + sessions, + ); +} + +export async function deactivate() { + // Everything above was registered through ctx and is disposed by core. +} diff --git a/plugins/vault/src/backend/profile-store.ts b/plugins/vault/src/backend/profile-store.ts new file mode 100644 index 000000000..d26db1baf --- /dev/null +++ b/plugins/vault/src/backend/profile-store.ts @@ -0,0 +1,139 @@ +import { randomUUID } from "node:crypto"; +import { desc, eq, or } from "drizzle-orm"; +import type { PluginContext } from "@termix/plugin-sdk/backend"; +import type { VaultProfileConfig } from "./vault-client.js"; + +/* eslint-disable @typescript-eslint/no-explicit-any */ +// ctx.db.define hands the table back untyped and the drizzle handle is the +// server's own, so both are typed at this module's edge. +type Table = any; +type Drizzle = any; +/* eslint-enable @typescript-eslint/no-explicit-any */ + +export const SYNC_ENTITY = "vaultProfiles"; + +export interface ProfileRow { + id: number; + userId: string; + name: string; + description: string | null; + folder: string | null; + tags: string | null; + vaultAddr: string; + vaultNamespace: string | null; + oidcMount: string | null; + oidcRole: string | null; + sshMount: string | null; + sshRole: string; + validPrincipals: string | null; + keyType: string | null; + shared: boolean | number; + syncId: string | null; + createdAt: string; + updatedAt: string; +} + +export type ProfileInput = Partial< + Omit +>; + +export function toConfig(row: ProfileRow): VaultProfileConfig { + return { + id: row.id, + vaultAddr: row.vaultAddr, + vaultNamespace: row.vaultNamespace ?? null, + oidcMount: row.oidcMount ?? null, + oidcRole: row.oidcRole ?? null, + sshMount: row.sshMount ?? null, + sshRole: row.sshRole, + validPrincipals: row.validPrincipals ?? null, + keyType: row.keyType ?? null, + }; +} + +export type ProfileStore = ReturnType; + +export function createProfileStore( + ctx: PluginContext, + table: Table, + tokensTable: Table, +) { + const client = () => ctx.db.client(); + + async function findById(id: number): Promise { + const rows = await ( + await client() + ) + .select() + .from(table) + .where(eq(table.id, id)) + .limit(1); + return rows[0] ?? null; + } + + async function findBySyncId(syncId: string): Promise { + const rows = await ( + await client() + ) + .select() + .from(table) + .where(eq(table.syncId, syncId)) + .limit(1); + return rows[0] ?? null; + } + + return { + findById, + + /** The user's own profiles plus every shared one. */ + async listVisible(userId: string): Promise { + return (await client()) + .select() + .from(table) + .where(or(eq(table.userId, userId), eq(table.shared, true))) + .orderBy(desc(table.updatedAt)); + }, + + async create( + userId: string, + input: ProfileInput & { + name: string; + vaultAddr: string; + sshRole: string; + }, + ): Promise { + const syncId = randomUUID(); + const now = new Date().toISOString(); + await (await client()).insert(table).values({ + ...input, + userId, + shared: input.shared ? true : false, + syncId, + createdAt: now, + updatedAt: now, + }); + await ctx.db.persist(); + return (await findBySyncId(syncId)) as ProfileRow; + }, + + async update(id: number, input: ProfileInput): Promise { + await ( + await client() + ) + .update(table) + .set({ ...input, updatedAt: new Date().toISOString() }) + .where(eq(table.id, id)); + await ctx.db.persist(); + return findById(id); + }, + + /** Deletes the profile, its cached certificates and records a tombstone. */ + async remove(row: ProfileRow): Promise { + const db = await client(); + await db.delete(tokensTable).where(eq(tokensTable.profileId, row.id)); + await db.delete(table).where(eq(table.id, row.id)); + await ctx.db.persist(); + await ctx.sync.recordTombstone(row.userId, SYNC_ENTITY, row.syncId); + }, + }; +} diff --git a/plugins/vault/src/backend/provider.ts b/plugins/vault/src/backend/provider.ts new file mode 100644 index 000000000..710bffb2b --- /dev/null +++ b/plugins/vault/src/backend/provider.ts @@ -0,0 +1,124 @@ +import type { + PluginContext, + PluginSshAuthProvider, + PluginSshHost, +} from "@termix/plugin-sdk/backend"; +import { applyCertificateAuth } from "@termix/plugin-sdk/ssh-certs"; +import type { AuthSessions, SignInSocket } from "./auth-session.js"; +import { + toConfig, + type ProfileRow, + type ProfileStore, +} from "./profile-store.js"; +import type { TokenStore } from "./token-store.js"; + +export const AUTH_TYPE = "vault"; + +const REQUIRED_MESSAGE = + "Vault SSH signer authentication required. Please open a Terminal connection first."; + +const AUTH_FAILED_PATTERN = /All configured authentication methods failed/i; + +/** The profile a host points at, from its host settings. */ +export async function profileForHost( + ctx: PluginContext, + profiles: ProfileStore, + hostId: number, +): Promise { + const raw = await ctx.settings.getHost(hostId, "profileId"); + const id = Number(raw); + if (!Number.isInteger(id) || id <= 0) return null; + return profiles.findById(id); +} + +/** + * Hosts with auth type "vault" connect with a certificate Vault signed for + * the host's profile. Without one, the terminal is asked for a browser + * sign-in ("vault_auth_required"), which startInteraction runs. + */ +export function createVaultProvider( + ctx: PluginContext, + profiles: ProfileStore, + tokens: TokenStore, + sessions: AuthSessions, +): PluginSshAuthProvider { + return { + type: AUTH_TYPE, + labelKey: "hosts.filterAuthVault", + needsUserInteraction: true, + supportsBackground: false, + interaction: AUTH_TYPE, + prepare: async (config, host: PluginSshHost, env) => { + const profile = await profileForHost(ctx, profiles, host.id); + if (!profile) { + return { + status: "error", + code: "failed", + message: "Host has no Vault signer profile configured", + }; + } + const cert = await tokens.get(env.userId, profile.id); + if (!cert) { + env.log( + "info", + "No valid Vault certificate found, requesting authentication", + ); + return { + status: "interaction-required", + interaction: AUTH_TYPE, + message: REQUIRED_MESSAGE, + flag: "requiresVaultAuth", + }; + } + try { + await applyCertificateAuth( + config as never, + env.client as never, + { privateKey: cert.privateKey, certificate: cert.sshCert }, + host.username, + ); + } catch (error) { + return { + status: "error", + code: "failed", + message: + "Vault SSH signer authentication failed: " + + (error instanceof Error ? error.message : String(error)), + }; + } + env.log("info", "Using cached Vault-signed certificate"); + return { status: "ready" }; + }, + onAuthFailed: (host, env, context) => { + if (!AUTH_FAILED_PATTERN.test(context.error.message)) return undefined; + ctx.log.warn("Vault certificate authentication failed, forgetting it"); + void profileForHost(ctx, profiles, host.id) + .then((profile) => + profile ? tokens.remove(env.userId, profile.id) : undefined, + ) + .catch(() => undefined); + return { + status: "interaction-required", + interaction: AUTH_TYPE, + message: + "Vault authentication failed or expired. Please authenticate again.", + }; + }, + startInteraction: async (request) => { + const profile = await profileForHost(ctx, profiles, request.hostId); + if (!profile) { + throw new Error("No Vault signer profile configured for this host"); + } + await sessions.start({ + userId: request.userId, + hostId: request.hostId, + profile: toConfig(profile), + socket: request.socket as SignInSocket, + requestOrigin: request.requestOrigin, + }); + }, + cancelInteraction: (request) => { + sessions.cancel(request.userId, request.hostId, request.requestId); + }, + }; +} diff --git a/plugins/vault/src/backend/routes.ts b/plugins/vault/src/backend/routes.ts new file mode 100644 index 000000000..aeb593295 --- /dev/null +++ b/plugins/vault/src/backend/routes.ts @@ -0,0 +1,337 @@ +import type { Request, Response, Router } from "express"; +import type { PluginContext } from "@termix/plugin-sdk/backend"; +import type { AuthSessions } from "./auth-session.js"; +import type { + ProfileInput, + ProfileRow, + ProfileStore, +} from "./profile-store.js"; + +/** + * Vault sends the browser back here without a Termix session: the sign-in is + * matched by the state Vault issued. + */ +export const PUBLIC_PATHS = ["/oidc/callback"]; + +export function escapeHtml(value: string): string { + return value + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} + +export function resultPage(ok: boolean, message: string): string { + return `Termix + +

${ok ? "Vault sign-in complete" : "Vault sign-in failed"}

${escapeHtml(message)}

+${ok ? "" : ""}`; +} + +function isNonEmptyString(value: unknown): value is string { + return typeof value === "string" && value.trim().length > 0; +} + +function optional(value: unknown): string | null { + return typeof value === "string" && value.trim() ? value.trim() : null; +} + +function formatProfile(row: ProfileRow, userId: string) { + return { + id: row.id, + name: row.name, + description: row.description, + folder: row.folder, + tags: row.tags ? row.tags.split(",").filter(Boolean) : [], + vaultAddr: row.vaultAddr, + vaultNamespace: row.vaultNamespace, + oidcMount: row.oidcMount, + oidcRole: row.oidcRole, + sshMount: row.sshMount, + sshRole: row.sshRole, + validPrincipals: row.validPrincipals, + keyType: row.keyType, + shared: !!row.shared, + owned: row.userId === userId, + createdAt: row.createdAt, + updatedAt: row.updatedAt, + }; +} + +function parseId(req: Request): number | null { + const id = Number.parseInt(String(req.params.id), 10); + return Number.isFinite(id) ? id : null; +} + +export function registerRoutes( + ctx: PluginContext, + router: Router, + profiles: ProfileStore, + sessions: AuthSessions, +): void { + const userId = () => ctx.currentActor() as string; + + /** + * @openapi + * /plugin-api/vault/oidc/callback: + * get: + * summary: Vault OIDC callback + * description: Public. The redirect URI a Vault OIDC role allows. Matches the sign-in by the state Vault issued, has Vault sign the ephemeral key and tells the terminal to reconnect. The 2.8 URI /vault/oidc/callback redirects here. + * tags: [Vault] + * parameters: + * - in: query + * name: state + * schema: + * type: string + * - in: query + * name: code + * schema: + * type: string + * - in: query + * name: error + * schema: + * type: string + * responses: + * 200: + * description: Signed in; an HTML page that closes itself. + * 400: + * description: The sign-in failed or is no longer active. + */ + router.get("/oidc/callback", async (req: Request, res: Response) => { + const state = typeof req.query.state === "string" ? req.query.state : ""; + const code = typeof req.query.code === "string" ? req.query.code : ""; + const error = typeof req.query.error === "string" ? req.query.error : ""; + + const reply = (ok: boolean, message: string) => + res + .status(ok ? 200 : 400) + .type("html") + .send(resultPage(ok, message)); + + if (error) return reply(false, `Vault returned: ${error}`); + if (!state || !code) return reply(false, "Missing state or code."); + const result = await sessions.complete(state, code); + return reply(result.ok, result.message); + }); + + router.use("/profiles", ctx.rbac.require("use") as never); + + /** + * @openapi + * /plugin-api/vault/profiles: + * get: + * summary: List Vault profiles + * description: The caller's own Vault signer profiles and every shared one. Needs vault.use. + * tags: [Vault] + * responses: + * 200: + * description: Array of Vault profile objects. + * 403: + * description: Missing vault.use. + */ + router.get("/profiles", async (_req: Request, res: Response) => { + const rows = await profiles.listVisible(userId()); + res.json(rows.map((row) => formatProfile(row, userId()))); + }); + + /** + * @openapi + * /plugin-api/vault/profiles: + * post: + * summary: Create a Vault profile + * description: Creates a Vault signer profile owned by the caller. Needs vault.use, and vault.share to mark it shared. + * tags: [Vault] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: [name, vaultAddr, sshRole] + * properties: + * name: + * type: string + * vaultAddr: + * type: string + * vaultNamespace: + * type: string + * oidcMount: + * type: string + * oidcRole: + * type: string + * sshMount: + * type: string + * sshRole: + * type: string + * validPrincipals: + * type: string + * keyType: + * type: string + * shared: + * type: boolean + * responses: + * 201: + * description: The created profile. + * 400: + * description: Missing required fields. + * 403: + * description: Missing vault.use, or vault.share for a shared profile. + */ + router.post("/profiles", async (req: Request, res: Response) => { + const body = (req.body ?? {}) as Record; + if ( + !isNonEmptyString(body.name) || + !isNonEmptyString(body.vaultAddr) || + !isNonEmptyString(body.sshRole) + ) { + return res + .status(400) + .json({ error: "name, vaultAddr and sshRole are required" }); + } + const shared = !!body.shared; + if (shared && !(await ctx.rbac.has("share"))) { + return res + .status(403) + .json({ error: "You are not allowed to share Vault profiles" }); + } + const row = await profiles.create(userId(), { + name: body.name.trim(), + description: optional(body.description), + folder: optional(body.folder), + tags: Array.isArray(body.tags) + ? body.tags.join(",") + : typeof body.tags === "string" + ? body.tags + : "", + vaultAddr: body.vaultAddr.trim(), + vaultNamespace: optional(body.vaultNamespace), + oidcMount: optional(body.oidcMount), + oidcRole: optional(body.oidcRole), + sshMount: optional(body.sshMount), + sshRole: body.sshRole.trim(), + validPrincipals: optional(body.validPrincipals), + keyType: optional(body.keyType), + shared, + }); + res.status(201).json(formatProfile(row, userId())); + }); + + /** + * @openapi + * /plugin-api/vault/profiles/{id}: + * put: + * summary: Update a Vault profile + * description: Only the owner may edit a profile. Needs vault.use, and vault.share to mark it shared. + * tags: [Vault] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * requestBody: + * content: + * application/json: + * schema: + * type: object + * responses: + * 200: + * description: The updated profile. + * 400: + * description: Invalid profile id. + * 403: + * description: Not the owner, or sharing without vault.share. + * 404: + * description: Profile not found. + */ + router.put("/profiles/:id", async (req: Request, res: Response) => { + const id = parseId(req); + if (id === null) + return res.status(400).json({ error: "Invalid profile id" }); + const existing = await profiles.findById(id); + if (!existing) return res.status(404).json({ error: "Profile not found" }); + if (existing.userId !== userId()) { + return res + .status(403) + .json({ error: "Only the owner can edit this profile" }); + } + + const body = (req.body ?? {}) as Record; + const fields: ProfileInput = {}; + if (isNonEmptyString(body.name)) fields.name = body.name.trim(); + if (body.description !== undefined) + fields.description = optional(body.description); + if (body.folder !== undefined) fields.folder = optional(body.folder); + if (body.tags !== undefined) { + fields.tags = Array.isArray(body.tags) + ? body.tags.join(",") + : typeof body.tags === "string" + ? body.tags + : ""; + } + if (isNonEmptyString(body.vaultAddr)) + fields.vaultAddr = body.vaultAddr.trim(); + if (body.vaultNamespace !== undefined) + fields.vaultNamespace = optional(body.vaultNamespace); + if (body.oidcMount !== undefined) + fields.oidcMount = optional(body.oidcMount); + if (body.oidcRole !== undefined) fields.oidcRole = optional(body.oidcRole); + if (body.sshMount !== undefined) fields.sshMount = optional(body.sshMount); + if (isNonEmptyString(body.sshRole)) fields.sshRole = body.sshRole.trim(); + if (body.validPrincipals !== undefined) + fields.validPrincipals = optional(body.validPrincipals); + if (body.keyType !== undefined) fields.keyType = optional(body.keyType); + if (body.shared !== undefined) { + if (body.shared && !(await ctx.rbac.has("share"))) { + return res + .status(403) + .json({ error: "You are not allowed to share Vault profiles" }); + } + fields.shared = !!body.shared; + } + + const updated = await profiles.update(id, fields); + if (!updated) return res.status(404).json({ error: "Profile not found" }); + res.json(formatProfile(updated, userId())); + }); + + /** + * @openapi + * /plugin-api/vault/profiles/{id}: + * delete: + * summary: Delete a Vault profile + * description: Deletes a profile and every certificate cached for it. Only the owner may delete it. Needs vault.use. + * tags: [Vault] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * responses: + * 200: + * description: Deleted. + * 400: + * description: Invalid profile id. + * 403: + * description: Not the owner. + * 404: + * description: Profile not found. + */ + router.delete("/profiles/:id", async (req: Request, res: Response) => { + const id = parseId(req); + if (id === null) + return res.status(400).json({ error: "Invalid profile id" }); + const existing = await profiles.findById(id); + if (!existing) return res.status(404).json({ error: "Profile not found" }); + if (existing.userId !== userId()) { + return res + .status(403) + .json({ error: "Only the owner can delete this profile" }); + } + await profiles.remove(existing); + res.json({ success: true }); + }); +} diff --git a/plugins/vault/src/backend/tables.ts b/plugins/vault/src/backend/tables.ts new file mode 100644 index 000000000..f4fdbbf84 --- /dev/null +++ b/plugins/vault/src/backend/tables.ts @@ -0,0 +1,74 @@ +import { + adoptLegacyTable, + boolean, + defineTable, + id, + integer, + refUser, + text, + varchar, +} from "@termix/plugin-sdk/db"; + +/** + * Vault signer profiles: connection settings only, no secrets. A shared + * profile is visible to every user. Adopted from core's vault_profiles. + */ +export const profiles = adoptLegacyTable( + "vault_profiles", + defineTable("profiles", { + id: id(), + userId: refUser(), + name: text().notNull(), + description: text(), + folder: text(), + tags: text(), + vaultAddr: text().notNull(), + vaultNamespace: text(), + oidcMount: text(), + oidcRole: text(), + sshMount: text(), + sshRole: text().notNull(), + validPrincipals: text(), + keyType: text(), + shared: boolean().notNull().default(false), + syncId: varchar().unique(), + createdAt: text().notNull().defaultNow(), + updatedAt: text().notNull().defaultNow(), + }), +); + +/** + * One signed certificate and its ephemeral key per user and profile, sealed + * with ctx.secrets.seal. Adopted from core's vault_tokens. Rows 2.8 wrote were + * encrypted with the user's data key; they do not unseal and are dropped. + * + * profileId has no foreign key on a fresh install (the SDK has no builder + * for one to the plugin's own table), so deleting a profile deletes its + * tokens in code. Upgraded installs keep 2.8's cascading key. + */ +export const tokens = adoptLegacyTable( + "vault_tokens", + defineTable( + "tokens", + { + id: id(), + userId: refUser(), + profileId: integer().notNull(), + sshCert: text().notNull(), + privateKey: text().notNull(), + createdAt: text().notNull().defaultNow(), + expiresAt: varchar().notNull(), + lastUsed: text(), + }, + { + uniques: [ + { + name: "idx_vault_tokens_user_profile", + columns: ["userId", "profileId"], + }, + ], + }, + ), +); + +export const tables = [profiles, tokens]; diff --git a/plugins/vault/src/backend/token-store.ts b/plugins/vault/src/backend/token-store.ts new file mode 100644 index 000000000..8d4028820 --- /dev/null +++ b/plugins/vault/src/backend/token-store.ts @@ -0,0 +1,114 @@ +import { and, eq } from "drizzle-orm"; +import type { PluginContext } from "@termix/plugin-sdk/backend"; +import { parseCertValidBefore } from "./vault-client.js"; + +/* eslint-disable @typescript-eslint/no-explicit-any */ +type Table = any; +type Drizzle = any; +/* eslint-enable @typescript-eslint/no-explicit-any */ + +// Sign again once a certificate is this close to expiring. +const EXPIRY_SKEW_MS = 60 * 1000; +// When the certificate's expiry cannot be read. +const FALLBACK_TTL_MS = 5 * 60 * 1000; + +export interface CachedCertificate { + privateKey: string; + sshCert: string; +} + +export type TokenStore = ReturnType; + +export function createTokenStore(ctx: PluginContext, table: Table) { + const client = () => ctx.db.client(); + const match = (userId: string, profileId: number) => + and(eq(table.userId, userId), eq(table.profileId, profileId)); + + async function find(userId: string, profileId: number) { + const rows = await ( + await client() + ) + .select() + .from(table) + .where(match(userId, profileId)) + .limit(1); + return rows[0] ?? null; + } + + async function remove(userId: string, profileId: number): Promise { + if (!(await find(userId, profileId))) return; + await (await client()).delete(table).where(match(userId, profileId)); + await ctx.db.persist(); + } + + return { + /** Stores a signed certificate and returns when it expires. */ + async save( + userId: string, + profileId: number, + privateKey: string, + sshCert: string, + ): Promise { + const validBefore = parseCertValidBefore(sshCert); + const expiresAt = new Date( + validBefore > 0 ? validBefore * 1000 : Date.now() + FALLBACK_TTL_MS, + ).toISOString(); + const values = { + sshCert: await ctx.secrets.seal(sshCert), + privateKey: await ctx.secrets.seal(privateKey), + createdAt: new Date().toISOString(), + expiresAt, + lastUsed: null, + }; + if (await find(userId, profileId)) { + await ( + await client() + ) + .update(table) + .set(values) + .where(match(userId, profileId)); + } else { + await ( + await client() + ) + .insert(table) + .values({ userId, profileId, ...values }); + } + await ctx.db.persist(); + return expiresAt; + }, + + /** A usable certificate, or null. Expired or unreadable rows are deleted. */ + async get( + userId: string, + profileId: number, + ): Promise { + const row = await find(userId, profileId); + if (!row) return null; + const expiresAt = new Date(row.expiresAt).getTime(); + if ( + !Number.isFinite(expiresAt) || + expiresAt - EXPIRY_SKEW_MS < Date.now() + ) { + await remove(userId, profileId); + return null; + } + const sshCert = await ctx.secrets.unseal(row.sshCert); + const privateKey = await ctx.secrets.unseal(row.privateKey); + if (!sshCert || !privateKey) { + await remove(userId, profileId); + return null; + } + await ( + await client() + ) + .update(table) + .set({ lastUsed: new Date().toISOString() }) + .where(match(userId, profileId)); + await ctx.db.persist({ lazy: true }); + return { sshCert, privateKey }; + }, + + remove, + }; +} diff --git a/src/backend/hosts/vault-signer-core.ts b/plugins/vault/src/backend/vault-client.ts similarity index 71% rename from src/backend/hosts/vault-signer-core.ts rename to plugins/vault/src/backend/vault-client.ts index b1e569e0a..2d2216593 100644 --- a/src/backend/hosts/vault-signer-core.ts +++ b/plugins/vault/src/backend/vault-client.ts @@ -1,16 +1,15 @@ -// Pure (DB-free) HashiCorp Vault SSH signer logic: ephemeral key generation, -// the Vault OIDC HTTP flow, certificate signing, and certificate parsing. -// -// Kept separate from vault-signer-auth.ts (which adds the encrypted per-user -// certificate cache) so this layer can be unit-tested without the native SQLite -// dependency. - -import crypto from "crypto"; +import crypto from "node:crypto"; import ssh2Pkg from "ssh2"; -import { sshLogger } from "../utils/logger.js"; +import type { PluginFetch } from "@termix/plugin-sdk/backend"; const { utils: ssh2Utils } = ssh2Pkg; +/** + * Vault's HTTP API for SSH signing: an OIDC login for a short-lived Vault + * token, then the SSH secrets engine signs an ephemeral public key. No Vault + * token or long-lived key is ever stored. + */ + export interface VaultProfileConfig { id: number; vaultAddr: string; @@ -28,6 +27,10 @@ export interface EphemeralKeyPair { publicKey: string; } +/* eslint-disable @typescript-eslint/no-explicit-any */ +type VaultJson = any; +/* eslint-enable @typescript-eslint/no-explicit-any */ + export function normalizeAddr(addr: string): string { return addr.trim().replace(/\/+$/, ""); } @@ -47,12 +50,26 @@ function vaultHeaders(profile: VaultProfileConfig): Record { return headers; } +/** + * The profile's own Vault server may be private: someone configured it by + * name, which 2.8 allowed too. Nothing else private is reachable. + */ +export function allowedHosts(profile: VaultProfileConfig): string[] { + try { + return [new URL(normalizeAddr(profile.vaultAddr)).hostname]; + } catch { + return []; + } +} + async function vaultRequest( + fetch: PluginFetch, + profile: VaultProfileConfig, url: string, - method: "GET" | "POST" | "PUT", + method: "GET" | "POST", headers: Record, body?: Record, -): Promise { +): Promise { let response: Response; try { response = await fetch(url, { @@ -62,6 +79,7 @@ async function vaultRequest( ...headers, }, body: body ? JSON.stringify(body) : undefined, + allowPrivateHosts: allowedHosts(profile), }); } catch (e) { throw new Error( @@ -72,12 +90,12 @@ async function vaultRequest( } const text = await response.text(); - let json: any; + let json: VaultJson; if (text) { try { json = JSON.parse(text); } catch { - // leave undefined for non-JSON bodies + // not JSON } } @@ -91,37 +109,34 @@ async function vaultRequest( return json; } -/** Generate an ephemeral SSH keypair in OpenSSH format. */ +/** An ephemeral SSH keypair in OpenSSH format. */ export function generateEphemeralKeyPair( keyType?: string | null, ): EphemeralKeyPair { - let ssh2Type: "ed25519" | "rsa" | "ecdsa" = "ed25519"; + let type: "ed25519" | "rsa" | "ecdsa" = "ed25519"; const options: { bits?: number } = {}; switch ((keyType || "ssh-ed25519").trim()) { case "ssh-rsa": - ssh2Type = "rsa"; + type = "rsa"; options.bits = 4096; break; case "ecdsa-sha2-nistp256": - ssh2Type = "ecdsa"; + type = "ecdsa"; options.bits = 256; break; - case "ssh-ed25519": default: - ssh2Type = "ed25519"; - break; + type = "ed25519"; } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const pair = ssh2Utils.generateKeyPairSync(ssh2Type as any, options); + const pair = ssh2Utils.generateKeyPairSync(type as never, options as never); return { privateKey: pair.private, publicKey: pair.public }; } /** - * Start a Vault OIDC login: returns the IdP auth URL plus the state/nonce - * needed to complete the callback. `redirectUri` must be allowed both by the - * Vault role's allowed_redirect_uris and by the IdP. + * Starts a Vault OIDC login. `redirectUri` must be allowed by the Vault + * role's allowed_redirect_uris and by the identity provider. */ export async function startVaultOidc( + fetch: PluginFetch, profile: VaultProfileConfig, redirectUri: string, ): Promise<{ authUrl: string; state: string; clientNonce: string }> { @@ -130,6 +145,8 @@ export async function startVaultOidc( const clientNonce = crypto.randomBytes(20).toString("hex"); const json = await vaultRequest( + fetch, + profile, `${addr}/v1/auth/${mount}/oidc/auth_url`, "POST", vaultHeaders(profile), @@ -145,8 +162,7 @@ export async function startVaultOidc( throw new Error("Vault did not return an OIDC auth_url"); } - // Vault embeds the state it generated in the auth_url; we need it to correlate - // the browser callback back to this login attempt. + // Vault puts its own state in the auth_url; the callback is matched on it. let state = ""; try { state = new URL(authUrl).searchParams.get("state") || ""; @@ -161,8 +177,9 @@ export async function startVaultOidc( return { authUrl, state, clientNonce }; } -/** Complete the Vault OIDC callback and return a short-lived Vault token. */ +/** Completes the OIDC callback and returns a short-lived Vault token. */ export async function completeVaultOidc( + fetch: PluginFetch, profile: VaultProfileConfig, params: { state: string; code: string; clientNonce: string }, ): Promise { @@ -174,7 +191,13 @@ export async function completeVaultOidc( url.searchParams.set("code", params.code); url.searchParams.set("client_nonce", params.clientNonce); - const json = await vaultRequest(url.toString(), "GET", vaultHeaders(profile)); + const json = await vaultRequest( + fetch, + profile, + url.toString(), + "GET", + vaultHeaders(profile), + ); const token: string | undefined = json?.auth?.client_token; if (!token) { throw new Error("Vault OIDC callback did not return a client token"); @@ -182,8 +205,9 @@ export async function completeVaultOidc( return token; } -/** Sign an SSH public key with Vault, returning the OpenSSH certificate line. */ +/** Signs an SSH public key with Vault, returning the OpenSSH certificate line. */ export async function signWithVault( + fetch: PluginFetch, profile: VaultProfileConfig, vaultToken: string, publicKey: string, @@ -191,7 +215,6 @@ export async function signWithVault( const addr = normalizeAddr(profile.vaultAddr); const mount = trimMount(profile.sshMount, "ssh-client-signer"); - const headers = { ...vaultHeaders(profile), "X-Vault-Token": vaultToken }; const body: Record = { public_key: publicKey.trim(), cert_type: "user", @@ -201,9 +224,11 @@ export async function signWithVault( } const json = await vaultRequest( + fetch, + profile, `${addr}/v1/${mount}/sign/${encodeURIComponent(profile.sshRole.trim())}`, "POST", - headers, + { ...vaultHeaders(profile), "X-Vault-Token": vaultToken }, body, ); const signedKey: string | undefined = json?.data?.signed_key; @@ -214,8 +239,8 @@ export async function signWithVault( } /** - * Parse the "valid before" Unix timestamp out of an OpenSSH certificate. - * Returns 0 if it can't be parsed (caller falls back to a conservative TTL). + * The "valid before" Unix timestamp of an OpenSSH certificate, or 0 when it + * cannot be parsed (the caller then uses a short fallback). */ export function parseCertValidBefore(signedKey: string): number { try { @@ -225,7 +250,7 @@ export function parseCertValidBefore(signedKey: string): number { const blob = Buffer.from(parts[1], "base64"); let pos = 0; - const readString = (): void => { + const skipString = (): void => { const len = blob.readUInt32BE(pos); pos += 4 + len; }; @@ -236,8 +261,8 @@ export function parseCertValidBefore(signedKey: string): number { return hi * 0x100000000 + lo; }; - readString(); // format id - readString(); // nonce + skipString(); // format id + skipString(); // nonce let keyFields: number; if (certType.startsWith("ssh-ed25519")) keyFields = 1; @@ -247,19 +272,15 @@ export function parseCertValidBefore(signedKey: string): number { else if (certType.startsWith("sk-ssh-ed25519")) keyFields = 2; else if (certType.startsWith("sk-ecdsa-sha2-")) keyFields = 3; else return 0; - for (let i = 0; i < keyFields; i++) readString(); + for (let i = 0; i < keyFields; i++) skipString(); readUint64(); // serial - pos += 4; // type (uint32) - readString(); // key id - readString(); // valid principals + pos += 4; // type + skipString(); // key id + skipString(); // valid principals readUint64(); // valid after - return readUint64(); // valid before - } catch (e) { - sshLogger.warn("Failed to parse Vault-signed certificate expiry", { - operation: "vault_cert_parse", - error: e instanceof Error ? e.message : String(e), - }); + return readUint64(); + } catch { return 0; } } diff --git a/plugins/vault/src/frontend/RedirectUriSetting.tsx b/plugins/vault/src/frontend/RedirectUriSetting.tsx new file mode 100644 index 000000000..65b9a0e8b --- /dev/null +++ b/plugins/vault/src/frontend/RedirectUriSetting.tsx @@ -0,0 +1,54 @@ +import { toast } from "sonner"; +import { + useTranslation, + type SettingsComponentProps, +} from "@termix/plugin-sdk/frontend"; +import { Button, Input, copyToClipboard } from "@termix/plugin-sdk/ui"; + +const CALLBACK_PATH = "plugin-api/vault/oidc/callback"; +const LEGACY_CALLBACK_PATH = "vault/oidc/callback"; + +/** The redirect URI to allow in the Vault OIDC role. */ +export function redirectUri( + legacy: boolean, + baseUri = document.baseURI, +): string { + return new URL( + legacy ? LEGACY_CALLBACK_PATH : CALLBACK_PATH, + baseUri, + ).toString(); +} + +export function RedirectUriSetting({ values }: SettingsComponentProps) { + const { t } = useTranslation(); + const uri = redirectUri(values.legacyCallback === true); + + return ( +
+ + {t("settings.redirectUri.label")} + +
+ + +
+

+ {t("settings.redirectUri.description")} +

+
+ ); +} diff --git a/plugins/vault/src/frontend/VaultAuthEditor.tsx b/plugins/vault/src/frontend/VaultAuthEditor.tsx new file mode 100644 index 000000000..75633e050 --- /dev/null +++ b/plugins/vault/src/frontend/VaultAuthEditor.tsx @@ -0,0 +1,108 @@ +import { useCallback, useEffect, useState } from "react"; +import { + usePermission, + usePluginApi, + useTranslation, + type SshAuthEditorProps, +} from "@termix/plugin-sdk/frontend"; +import { Select2 } from "@termix/plugin-sdk/ui"; +import { VaultProfileManager } from "./VaultProfileManager"; +import type { VaultProfile } from "./types"; + +const PLUGIN_ID = "vault"; +const DOCS_URL = "https://docs.termix.site/features/authentication/vault"; + +type PluginSettingsBag = Record>; + +/** + * The host editor's "vault" auth method: pick a signer profile, and manage + * profiles in place. The choice is the profileId host setting, which the + * editor saves with the rest of the host's plugin settings. + */ +export function VaultAuthEditor({ form, setField }: SshAuthEditorProps) { + const { t } = useTranslation(); + const api = usePluginApi(); + const canUse = usePermission("use"); + const [profiles, setProfiles] = useState([]); + const [showManager, setShowManager] = useState(false); + + const bag = (form.pluginSettings as PluginSettingsBag | undefined) ?? {}; + const profileId = bag[PLUGIN_ID]?.profileId; + + const reload = useCallback(() => { + if (!canUse) return; + api + .get("/profiles") + .then(({ data }) => setProfiles(Array.isArray(data) ? data : [])) + .catch(() => setProfiles([])); + }, [api, canUse]); + + useEffect(() => { + reload(); + }, [reload]); + + const select = (value: string) => { + setField("pluginSettings", { + ...bag, + [PLUGIN_ID]: { + ...(bag[PLUGIN_ID] ?? {}), + profileId: value ? Number(value) : null, + }, + }); + }; + + return ( + <> +
+ + select(e.target.value)} + className="flex h-9 w-full border border-border bg-background px-3 py-1 text-xs outline-none focus:ring-1 focus:ring-ring" + > + + {profiles.map((profile) => ( + + ))} + +
+

+ {t("editor.hint")} +

+
+ + {t("editor.docs")} + + {canUse && ( + + )} +
+
+
+ {showManager && ( + setShowManager(false)} + /> + )} + + ); +} diff --git a/plugins/vault/src/frontend/VaultOverlay.tsx b/plugins/vault/src/frontend/VaultOverlay.tsx new file mode 100644 index 000000000..ea0d28952 --- /dev/null +++ b/plugins/vault/src/frontend/VaultOverlay.tsx @@ -0,0 +1,245 @@ +import { useEffect, useRef, useState } from "react"; +import { AlertCircle, KeyRound, Loader2 } from "lucide-react"; +import { useTranslation } from "@termix/plugin-sdk/frontend"; +import { Button } from "@termix/plugin-sdk/ui"; + +/** What the ssh-terminal plugin hands a "terminal.overlay" component. */ +export interface TerminalOverlayProps { + host: { id: number | string; [key: string]: unknown } | undefined; + backgroundColor?: string; + subscribe: ( + listener: (message: { type: string; [key: string]: unknown }) => void, + ) => () => void; + holdConnectTimeout: (held: boolean) => void; + fail: (message: string) => void; + disconnect: () => void; + send: (type: string, data?: unknown) => void; + connectPayload: () => Record; +} + +interface DialogState { + stage: "waiting" | "error"; + error?: string; + hostId?: unknown; + requestId?: string; +} + +/** How long the dialog waits before giving up on the connection. */ +const DIALOG_TIMEOUT_MS = 300_000; + +const footerButton = + "rounded-none text-[10px] font-bold uppercase tracking-widest"; + +/** + * Runs the Vault OIDC sign-in for a terminal: asks the server to start it, + * opens Vault's auth URL in a popup, and reconnects once Vault has signed + * the certificate. + */ +export function VaultOverlay({ + subscribe, + holdConnectTimeout, + fail, + disconnect, + send, + connectPayload, + backgroundColor, +}: TerminalOverlayProps) { + const { t } = useTranslation(); + const [dialog, setDialog] = useState(null); + const timerRef = useRef | null>(null); + const popupRef = useRef(null); + const heldRef = useRef(false); + // A second "auth required" after a sign-in means the certificate did not work. + const failedRef = useRef(false); + + useEffect(() => { + const clearTimer = () => { + if (timerRef.current) clearTimeout(timerRef.current); + timerRef.current = null; + }; + const closePopup = () => { + try { + popupRef.current?.close(); + } catch { + // already closed + } + popupRef.current = null; + }; + const hold = (held: boolean) => { + if (heldRef.current === held) return; + heldRef.current = held; + holdConnectTimeout(held); + }; + + const unsubscribe = subscribe((message) => { + switch (message.type) { + case "vault_auth_required": + hold(true); + if (failedRef.current) { + clearTimer(); + hold(false); + setDialog({ stage: "error", error: t("dialog.failed") }); + fail(t("dialog.failed")); + } else { + failedRef.current = true; + send("vault_start_auth", { hostId: message.hostId }); + } + break; + case "vault_auth_url": + try { + popupRef.current = window.open( + String(message.url ?? ""), + "termix-vault-oidc", + "width=540,height=720", + ); + } catch { + popupRef.current = null; + } + setDialog({ + stage: "waiting", + hostId: message.hostId, + requestId: + typeof message.requestId === "string" + ? message.requestId + : undefined, + }); + clearTimer(); + timerRef.current = setTimeout(() => { + setDialog(null); + hold(false); + disconnect(); + }, DIALOG_TIMEOUT_MS); + break; + case "vault_completed": + clearTimer(); + closePopup(); + setDialog(null); + send("vault_auth_completed", connectPayload()); + break; + case "vault_error": + failedRef.current = true; + clearTimer(); + closePopup(); + hold(false); + setDialog({ stage: "error", error: String(message.error ?? "") }); + break; + case "connected": + case "sessionAttached": + failedRef.current = false; + hold(false); + break; + case "session_closed": + clearTimer(); + setDialog(null); + hold(false); + break; + } + }); + + return () => { + unsubscribe(); + clearTimer(); + if (heldRef.current) { + heldRef.current = false; + holdConnectTimeout(false); + } + }; + }, [ + subscribe, + holdConnectTimeout, + fail, + disconnect, + send, + connectPayload, + t, + ]); + + if (!dialog) return null; + + const close = () => { + if (timerRef.current) clearTimeout(timerRef.current); + timerRef.current = null; + try { + popupRef.current?.close(); + } catch { + // already closed + } + if (dialog.stage === "waiting") { + send("vault_cancel", { + hostId: dialog.hostId, + requestId: dialog.requestId, + }); + } + setDialog(null); + if (heldRef.current) { + heldRef.current = false; + holdConnectTimeout(false); + } + }; + + return ( +
+
+
+
+
+ +

+ {dialog.stage === "error" + ? t("dialog.failed") + : t("dialog.title")} +

+
+
+
+ {dialog.stage === "waiting" ? ( +
+ +

+ {t("dialog.description")} +

+
+ ) : ( +
+ +

+ {dialog.error || t("dialog.failed")} +

+
+ )} +
+ {dialog.stage === "waiting" && ( + + )} + +
+
+
+
+ ); +} diff --git a/src/ui/sidebar/VaultProfileManager.tsx b/plugins/vault/src/frontend/VaultProfileManager.tsx similarity index 56% rename from src/ui/sidebar/VaultProfileManager.tsx rename to plugins/vault/src/frontend/VaultProfileManager.tsx index a19534bb4..16d2fe9c4 100644 --- a/src/ui/sidebar/VaultProfileManager.tsx +++ b/plugins/vault/src/frontend/VaultProfileManager.tsx @@ -1,21 +1,33 @@ -import { getErrorMessage } from "../lib/error-message.js"; import { useState } from "react"; -import { useTranslation } from "react-i18next"; import { Pencil, Plus, Trash2, X } from "lucide-react"; -import { toast } from "sonner"; - -import { Button } from "@/components/button"; -import { Input } from "@/components/input"; import { - createVaultProfile, - updateVaultProfile, - deleteVaultProfile, + usePermission, + usePluginApi, + useToast, + useTranslation, +} from "@termix/plugin-sdk/frontend"; +import { Button, Input, Select2 } from "@termix/plugin-sdk/ui"; +import { + errorMessage, + type VaultProfile, type VaultProfilePayload, -} from "@/main-axios"; -import type { VaultProfile } from "@/types/ui-types"; -import { Select2 } from "@/components/select2"; +} from "./types"; -type FormState = VaultProfilePayload & { id?: string }; +type FormState = { + id?: number; + name: string; + vaultAddr: string; + vaultNamespace: string; + oidcMount: string; + oidcRole: string; + sshMount: string; + sshRole: string; + validPrincipals: string; + keyType: string; + shared: boolean; +}; + +type TextKey = Exclude; const emptyForm: FormState = { name: "", @@ -46,6 +58,9 @@ function toForm(p: VaultProfile): FormState { }; } +const blank = (value: string) => value.trim() || null; + +/** Create, edit and delete the caller's Vault profiles. */ export function VaultProfileManager({ profiles, onChanged, @@ -56,16 +71,19 @@ export function VaultProfileManager({ onClose: () => void; }) { const { t } = useTranslation(); + const api = usePluginApi(); + const toast = useToast(); + const canShare = usePermission("share"); const [form, setForm] = useState(null); const [saving, setSaving] = useState(false); - const setField = (k: K, v: FormState[K]) => - setForm((p) => (p ? { ...p, [k]: v } : p)); + const setField = (key: K, value: FormState[K]) => + setForm((current) => (current ? { ...current, [key]: value } : current)); const handleSave = async () => { if (!form) return; if (!form.name.trim() || !form.vaultAddr.trim() || !form.sshRole.trim()) { - toast.error(t("hosts.vaultProfileValidationError")); + toast.error(t("profiles.validationError")); return; } setSaving(true); @@ -73,51 +91,51 @@ export function VaultProfileManager({ const payload: VaultProfilePayload = { name: form.name.trim(), vaultAddr: form.vaultAddr.trim(), - vaultNamespace: form.vaultNamespace?.trim() || null, - oidcMount: form.oidcMount?.trim() || null, - oidcRole: form.oidcRole?.trim() || null, - sshMount: form.sshMount?.trim() || null, + vaultNamespace: blank(form.vaultNamespace), + oidcMount: blank(form.oidcMount), + oidcRole: blank(form.oidcRole), + sshMount: blank(form.sshMount), sshRole: form.sshRole.trim(), - validPrincipals: form.validPrincipals?.trim() || null, - keyType: form.keyType?.trim() || null, - shared: !!form.shared, + validPrincipals: blank(form.validPrincipals), + keyType: blank(form.keyType), + shared: canShare && form.shared, }; if (form.id) { - await updateVaultProfile(Number(form.id), payload); - toast.success(t("hosts.vaultProfileSaved")); + await api.put(`/profiles/${form.id}`, payload); + toast.success(t("profiles.saved")); } else { - await createVaultProfile(payload); - toast.success(t("hosts.vaultProfileCreated")); + await api.post("/profiles", payload); + toast.success(t("profiles.created")); } setForm(null); onChanged(); - } catch (e) { - toast.error(getErrorMessage(e, t("hosts.vaultProfileSaveFailed"))); + } catch (error) { + toast.error(errorMessage(error, t("profiles.saveFailed"))); } finally { setSaving(false); } }; - const handleDelete = async (p: VaultProfile) => { + const handleDelete = async (profile: VaultProfile) => { try { - await deleteVaultProfile(Number(p.id)); - toast.success(t("hosts.vaultProfileDeleted")); + await api.delete(`/profiles/${profile.id}`); + toast.success(t("profiles.deleted")); onChanged(); - } catch (e) { - toast.error(getErrorMessage(e, t("hosts.vaultProfileDeleteFailed"))); + } catch (error) { + toast.error(errorMessage(error, t("profiles.deleteFailed"))); } }; - const field = (label: string, key: keyof FormState, placeholder?: string) => ( + const field = (label: string, key: TextKey, placeholder?: string) => (
setField(key, e.target.value as FormState[typeof key])} + value={form?.[key] ?? ""} + onChange={(e) => setField(key, e.target.value)} />
); @@ -126,10 +144,11 @@ export function VaultProfileManager({
- {t("hosts.vaultManageProfiles")} + {t("profiles.manage")} @@ -190,10 +209,10 @@ export function VaultProfileManager({ type="button" variant="outline" size="sm" - className="h-7 text-[10px] self-start" + className="h-7 text-[10px] self-start rounded-none" onClick={() => setForm({ ...emptyForm })} > - {t("hosts.vaultNewProfile")} + {t("profiles.new")} )} @@ -201,33 +220,25 @@ export function VaultProfileManager({ {form && (
- {field(t("hosts.friendlyNameLabel"), "name", "Production Vault")} + {field(t("profiles.name"), "name", "Production Vault")} + {field(t("profiles.vaultAddr"), "vaultAddr", "https://vault:8200")} + {field(t("profiles.namespace"), "vaultNamespace", "admin")} + {field(t("profiles.oidcMount"), "oidcMount", "oidc")} + {field(t("profiles.oidcRole"), "oidcRole", "default")} + {field(t("profiles.sshMount"), "sshMount", "ssh-client-signer")} + {field(t("profiles.sshRole"), "sshRole", "my-role")} {field( - t("hosts.vaultAddrLabel"), - "vaultAddr", - "https://vault:8200", - )} - {field(t("hosts.vaultNamespaceLabel"), "vaultNamespace", "admin")} - {field(t("hosts.vaultOidcMountLabel"), "oidcMount", "oidc")} - {field(t("hosts.vaultOidcRoleLabel"), "oidcRole", "default")} - {field( - t("hosts.vaultSshMountLabel"), - "sshMount", - "ssh-client-signer", - )} - {field(t("hosts.vaultSshRoleLabel"), "sshRole", "my-role")} - {field( - t("hosts.vaultValidPrincipalsLabel"), + t("profiles.validPrincipals"), "validPrincipals", "root,deploy", )}
setField("keyType", e.target.value)} className="flex h-8 w-full border border-border bg-background px-3 py-1 text-xs outline-none focus:ring-1 focus:ring-ring" > @@ -236,35 +247,36 @@ export function VaultProfileManager({
- + {canShare && ( + + )}
diff --git a/plugins/vault/src/frontend/index.tsx b/plugins/vault/src/frontend/index.tsx new file mode 100644 index 000000000..aec147cff --- /dev/null +++ b/plugins/vault/src/frontend/index.tsx @@ -0,0 +1,29 @@ +import type { ComponentType } from "react"; +import type { TermixApp } from "@termix/plugin-sdk/frontend"; +import { VaultAuthEditor } from "./VaultAuthEditor"; +import { VaultOverlay } from "./VaultOverlay"; +import { RedirectUriSetting } from "./RedirectUriSetting"; + +export function activate(app: TermixApp): void { + app.registerSshAuthEditor({ + authType: "vault", + titleKey: "hosts.filterAuthVault", + component: VaultAuthEditor, + }); + + // The Vault sign-in while a terminal connects to a Vault host. + app.registerSlotContribution("terminal.overlay", { + actionId: "vault.signIn", + titleKey: "dialog.title", + kind: "component", + component: VaultOverlay as unknown as ComponentType< + Record + >, + }); + + app.registerSettingsComponent("redirectUri", RedirectUriSetting); +} + +export function deactivate(): void { + // Registrations through app are disposed by core. +} diff --git a/plugins/vault/src/frontend/types.ts b/plugins/vault/src/frontend/types.ts new file mode 100644 index 000000000..2cb0e61f1 --- /dev/null +++ b/plugins/vault/src/frontend/types.ts @@ -0,0 +1,34 @@ +/** A Vault signer profile as GET /profiles returns it. */ +export interface VaultProfile { + id: number; + name: string; + vaultAddr: string; + vaultNamespace: string | null; + oidcMount: string | null; + oidcRole: string | null; + sshMount: string | null; + sshRole: string; + validPrincipals: string | null; + keyType: string | null; + shared: boolean; + owned: boolean; +} + +export interface VaultProfilePayload { + name: string; + vaultAddr: string; + vaultNamespace: string | null; + oidcMount: string | null; + oidcRole: string | null; + sshMount: string | null; + sshRole: string; + validPrincipals: string | null; + keyType: string | null; + shared: boolean; +} + +export function errorMessage(error: unknown, fallback: string): string { + const data = (error as { response?: { data?: { error?: unknown } } }) + ?.response?.data; + return typeof data?.error === "string" ? data.error : fallback; +} diff --git a/plugins/vault/tests/backend/helpers.ts b/plugins/vault/tests/backend/helpers.ts new file mode 100644 index 000000000..9d3d8ad30 --- /dev/null +++ b/plugins/vault/tests/backend/helpers.ts @@ -0,0 +1,174 @@ +import http from "node:http"; +import type { AddressInfo } from "node:net"; +import { fileURLToPath } from "node:url"; +import express, { type Router } from "express"; +import { + createMockCtx, + createTestDb, + type MockPluginContext, + type TestDb, + type TestDbOptions, +} from "@termix/plugin-sdk/testing"; +import type { + PluginFetchInit, + PluginSshAuthProvider, +} from "@termix/plugin-sdk/backend"; +import type { PluginManifest } from "@termix/plugin-sdk/manifest"; +import manifestJson from "../../manifest.json"; + +export const pluginDir = fileURLToPath(new URL("../..", import.meta.url)); +export const manifest = manifestJson as unknown as PluginManifest; + +export const VAULT_ADDR = "https://vault.internal:8200"; +export const SIGNED_CERT = "ssh-ed25519-cert-v01@openssh.com AAAAsigned"; + +/** A fake Vault answering the three calls the plugin makes. */ +export function createFakeVault( + options: { signStatus?: number; state?: string } = {}, +) { + const calls: Array<{ url: string; init?: PluginFetchInit }> = []; + const state = options.state ?? "vault-state-1"; + const json = (status: number, body: unknown) => + new Response(JSON.stringify(body), { status }); + + const fetch = async (url: string, init?: PluginFetchInit) => { + calls.push({ url, init }); + const { pathname } = new URL(url); + if (pathname === "/v1/auth/oidc/oidc/auth_url") { + return json(200, { + data: { + auth_url: `https://idp.test/authorize?client_id=termix&state=${state}`, + }, + }); + } + if (pathname === "/v1/auth/oidc/oidc/callback") { + return json(200, { auth: { client_token: "hvs.token" } }); + } + if (pathname.startsWith("/v1/ssh-client-signer/sign/")) { + if (options.signStatus && options.signStatus !== 200) { + return json(options.signStatus, { errors: ["permission denied"] }); + } + return json(200, { data: { signed_key: SIGNED_CERT } }); + } + return json(404, { errors: ["not found"] }); + }; + return { fetch, calls, state }; +} + +/** A fake terminal socket that records what it was sent. */ +export function fakeSocket() { + const sent: Array> = []; + return { + sent, + socket: { send: (data: string) => sent.push(JSON.parse(data)) }, + }; +} + +export interface TestServer { + db: TestDb; + mock: MockPluginContext; + provider: () => PluginSshAuthProvider; + request: ( + method: string, + path: string, + options?: { user?: string; body?: unknown }, + // Route bodies vary per test; asserting on them is the point. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ) => Promise<{ status: number; body: any; text: string }>; + close: () => Promise; +} + +/** + * The plugin activated against a real in-memory database, its router mounted + * the way core mounts it: JSON parsed and the acting user taken from the + * request. + */ +export async function startServer( + options: { + permissions?: string[]; + settings?: Record; + fetch?: (url: string, init?: PluginFetchInit) => Promise; + before?: TestDbOptions["before"]; + } = {}, +): Promise { + const db = await createTestDb(pluginDir, { before: options.before }); + for (const user of ["user-1", "user-2"]) { + db.sqlite.prepare("INSERT OR IGNORE INTO users (id) VALUES (?)").run(user); + } + db.sqlite.prepare("INSERT OR IGNORE INTO ssh_data (id) VALUES (?)").run(7); + + let router: Router | null = null; + const mock = createMockCtx({ + pluginId: manifest.id, + manifest, + capabilities: manifest.capabilities, + db: db.database, + router: () => (router = express.Router()), + permissions: options.permissions, + settings: options.settings, + fetch: options.fetch, + actor: "user-1", + }); + + const { activate } = await import("../../src/backend/index.js"); + await activate(mock.ctx); + + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { + mock.setActor(req.header("x-test-user") ?? undefined); + next(); + }); + app.use((req, res, next) => router!(req, res, next)); + const server = http.createServer(app); + await new Promise((resolve) => server.listen(0, resolve)); + const { port } = server.address() as AddressInfo; + + return { + db, + mock, + provider: () => + mock.auth.sshAuthProviders.find((provider) => provider.type === "vault")!, + async request(method, path, { user = "user-1", body } = {}) { + const response = await fetch(`http://127.0.0.1:${port}${path}`, { + method, + redirect: "manual", + headers: { + "x-test-user": user, + ...(body !== undefined ? { "content-type": "application/json" } : {}), + }, + body: body !== undefined ? JSON.stringify(body) : undefined, + }); + const text = await response.text(); + let parsed: unknown = null; + try { + parsed = text ? JSON.parse(text) : null; + } catch { + parsed = null; + } + return { status: response.status, body: parsed, text }; + }, + async close() { + await new Promise((resolve) => server.close(() => resolve())); + db.close(); + }, + }; +} + +export const env = (userId = "user-1", hostId = 7) => ({ + client: {}, + userId, + hostId, + purpose: "terminal", + interactive: true, + log: () => {}, +}); + +export const HOST = { id: 7, ip: "10.0.0.7", port: 22, username: "root" }; + +export const PROFILE = { + name: "Prod", + vaultAddr: VAULT_ADDR, + sshRole: "ops", + oidcRole: "dev", +}; diff --git a/plugins/vault/tests/backend/migrations.test.ts b/plugins/vault/tests/backend/migrations.test.ts new file mode 100644 index 000000000..02b0060d8 --- /dev/null +++ b/plugins/vault/tests/backend/migrations.test.ts @@ -0,0 +1,121 @@ +import { afterEach, describe, expect, it } from "vitest"; +import { createTestDb, type TestDb } from "@termix/plugin-sdk/testing"; +import { pluginDir } from "./helpers"; + +// vault_profiles and vault_tokens as core's SQLite bootstrap created them +// before 2.9.0, including the sync_id column added later. +const LEGACY_DDL = ` + CREATE TABLE vault_profiles ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id TEXT NOT NULL, + name TEXT NOT NULL, + description TEXT, + folder TEXT, + tags TEXT, + vault_addr TEXT NOT NULL, + vault_namespace TEXT, + oidc_mount TEXT, + oidc_role TEXT, + ssh_mount TEXT, + ssh_role TEXT NOT NULL, + valid_principals TEXT, + key_type TEXT, + shared INTEGER NOT NULL DEFAULT 0, + created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + sync_id TEXT, + FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE + ); + CREATE TABLE vault_tokens ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id TEXT NOT NULL, + profile_id INTEGER NOT NULL, + ssh_cert TEXT NOT NULL, + private_key TEXT NOT NULL, + created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + expires_at TEXT NOT NULL, + last_used TEXT, + UNIQUE(user_id, profile_id), + FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE, + FOREIGN KEY (profile_id) REFERENCES vault_profiles (id) ON DELETE CASCADE + ); +`; + +let db: TestDb | null = null; + +afterEach(() => { + db?.close(); + db = null; +}); + +const tableExists = (name: string) => + !!db!.sqlite + .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?") + .get(name); + +describe("adopting vault_profiles and vault_tokens", () => { + it("keeps every existing row when the legacy tables are there", async () => { + db = await createTestDb(pluginDir, { + before: (sqlite) => { + sqlite.exec("INSERT INTO users (id) VALUES ('user-1'), ('user-2')"); + sqlite.exec(LEGACY_DDL); + sqlite + .prepare( + "INSERT INTO vault_profiles (id, user_id, name, vault_addr, ssh_role, shared, sync_id) VALUES (?, ?, ?, ?, ?, ?, ?)", + ) + .run(4, "user-1", "Prod", "https://vault:8200", "ops", 1, "sync-a"); + sqlite + .prepare( + "INSERT INTO vault_tokens (user_id, profile_id, ssh_cert, private_key, expires_at) VALUES (?, ?, ?, ?, ?)", + ) + .run("user-2", 4, "cert", "key", "2999-01-01T00:00:00.000Z"); + }, + }); + + expect(tableExists("vault_profiles")).toBe(false); + expect(tableExists("vault_tokens")).toBe(false); + expect(db.sqlite.prepare("SELECT * FROM p_vault_profiles").all()).toEqual([ + expect.objectContaining({ + id: 4, + user_id: "user-1", + name: "Prod", + shared: 1, + sync_id: "sync-a", + }), + ]); + expect(db.sqlite.prepare("SELECT * FROM p_vault_tokens").all()).toEqual([ + expect.objectContaining({ user_id: "user-2", profile_id: 4 }), + ]); + + // The 2.8 foreign key follows the rename, so deleting a profile still + // takes its cached certificates with it. + db.sqlite.exec("PRAGMA foreign_keys = ON"); + db.sqlite.prepare("DELETE FROM p_vault_profiles WHERE id = 4").run(); + expect( + db.sqlite.prepare("SELECT COUNT(*) AS n FROM p_vault_tokens").get(), + ).toEqual({ n: 0 }); + }); + + it("creates both tables on a fresh install", async () => { + db = await createTestDb(pluginDir); + expect(tableExists("p_vault_profiles")).toBe(true); + expect(tableExists("p_vault_tokens")).toBe(true); + expect( + db.sqlite + .prepare( + "SELECT name FROM sqlite_master WHERE type = 'index' AND name = 'idx_vault_tokens_user_profile'", + ) + .get(), + ).toBeTruthy(); + }); + + it("keeps one certificate per user and profile", async () => { + db = await createTestDb(pluginDir); + db.sqlite.exec("INSERT INTO users (id) VALUES ('user-1')"); + const insert = db.sqlite.prepare( + "INSERT INTO p_vault_tokens (user_id, profile_id, ssh_cert, private_key, expires_at) VALUES ('user-1', 1, 'c', 'k', 'x')", + ); + insert.run(); + expect(() => insert.run()).toThrow(/UNIQUE/); + }); +}); diff --git a/src/backend/tests/hosts/vault-signer-core.test.ts b/plugins/vault/tests/backend/vault-client.test.ts similarity index 50% rename from src/backend/tests/hosts/vault-signer-core.test.ts rename to plugins/vault/tests/backend/vault-client.test.ts index 398750dd2..574b733ff 100644 --- a/src/backend/tests/hosts/vault-signer-core.test.ts +++ b/plugins/vault/tests/backend/vault-client.test.ts @@ -1,17 +1,19 @@ -import { describe, it, expect, beforeAll, afterEach, vi } from "vitest"; -import { execFileSync } from "child_process"; -import { mkdtempSync, readFileSync, rmSync } from "fs"; -import os from "os"; -import path from "path"; +import { describe, it, expect, beforeAll } from "vitest"; +import { execFileSync } from "node:child_process"; +import { mkdtempSync, readFileSync, rmSync } from "node:fs"; +import os from "node:os"; +import path from "node:path"; import ssh2Pkg from "ssh2"; +import type { PluginFetchInit } from "@termix/plugin-sdk/backend"; import { + allowedHosts, + completeVaultOidc, generateEphemeralKeyPair, parseCertValidBefore, - startVaultOidc, - completeVaultOidc, signWithVault, + startVaultOidc, type VaultProfileConfig, -} from "../../hosts/vault-signer-core.js"; +} from "../../src/backend/vault-client.js"; const { utils: ssh2Utils } = ssh2Pkg; @@ -25,12 +27,8 @@ describe("generateEphemeralKeyPair", () => { const pair = generateEphemeralKeyPair(keyType); expect(pair.privateKey).toContain("BEGIN OPENSSH PRIVATE KEY"); expect(pair.publicKey.split(/\s+/)[0]).toBe(keyType); - - // Both halves must be parseable by the same library that signs/connects. - const priv = ssh2Utils.parseKey(pair.privateKey); - expect(priv instanceof Error).toBe(false); - const pub = ssh2Utils.parseKey(pair.publicKey); - expect(pub instanceof Error).toBe(false); + expect(ssh2Utils.parseKey(pair.privateKey) instanceof Error).toBe(false); + expect(ssh2Utils.parseKey(pair.publicKey) instanceof Error).toBe(false); }); } @@ -48,24 +46,17 @@ describe("parseCertValidBefore", () => { beforeAll(() => { const dir = mkdtempSync(path.join(os.tmpdir(), "vault-cert-test-")); try { - execFileSync("ssh-keygen", [ - "-t", - "ed25519", - "-f", - `${dir}/ca`, - "-N", - "", - "-q", - ]); - execFileSync("ssh-keygen", [ - "-t", - "ed25519", - "-f", - `${dir}/user`, - "-N", - "", - "-q", - ]); + for (const name of ["ca", "user"]) { + execFileSync("ssh-keygen", [ + "-t", + "ed25519", + "-f", + `${dir}/${name}`, + "-N", + "", + "-q", + ]); + } signedAt = Math.floor(Date.now() / 1000); execFileSync("ssh-keygen", [ "-s", @@ -87,12 +78,8 @@ describe("parseCertValidBefore", () => { }); it("reads valid_before from a real ssh-keygen certificate", () => { - if (!haveSshKeygen) { - console.warn("ssh-keygen unavailable; skipping real-cert parse test"); - return; - } + if (!haveSshKeygen) return; const validBefore = parseCertValidBefore(cert); - // -V +60m => valid_before is roughly signedAt + 3600 (start rounds to minute) expect(validBefore).toBeGreaterThan(signedAt + 3300); expect(validBefore).toBeLessThan(signedAt + 3900); }); @@ -104,7 +91,7 @@ describe("parseCertValidBefore", () => { }); }); -describe("Vault HTTP flow (mocked fetch)", () => { +describe("the Vault HTTP flow", () => { const profile: VaultProfileConfig = { id: 1, vaultAddr: "https://vault.example.com:8200/", @@ -117,34 +104,23 @@ describe("Vault HTTP flow (mocked fetch)", () => { keyType: "ssh-ed25519", }; - afterEach(() => { - vi.unstubAllGlobals(); - }); - - function mockFetch( - impl: ( - url: string, - init: RequestInit, - ) => { status?: number; body: unknown }, - ) { - const calls: Array<{ url: string; init: RequestInit }> = []; - vi.stubGlobal( - "fetch", - vi.fn(async (url: string, init: RequestInit) => { - calls.push({ url, init }); - const { status = 200, body } = impl(url, init); - return { - ok: status >= 200 && status < 300, - status, - text: async () => JSON.stringify(body), - } as Response; - }), - ); - return calls; + function fakeFetch(respond: () => { status?: number; body: unknown }) { + const calls: Array<{ url: string; init?: PluginFetchInit }> = []; + const fetch = async (url: string, init?: PluginFetchInit) => { + calls.push({ url, init }); + const { status = 200, body } = respond(); + return new Response(JSON.stringify(body), { status }); + }; + return { fetch, calls }; } - it("startVaultOidc posts auth_url and extracts state", async () => { - const calls = mockFetch(() => ({ + it("allows only the profile's own Vault host past the private address guard", () => { + expect(allowedHosts(profile)).toEqual(["vault.example.com"]); + expect(allowedHosts({ ...profile, vaultAddr: "not a url" })).toEqual([]); + }); + + it("startVaultOidc posts auth_url and reads the state", async () => { + const { fetch, calls } = fakeFetch(() => ({ body: { data: { auth_url: @@ -154,33 +130,31 @@ describe("Vault HTTP flow (mocked fetch)", () => { })); const result = await startVaultOidc( + fetch, profile, - "https://termix/vault/oidc/callback", + "https://termix/plugin-api/vault/oidc/callback", ); expect(result.state).toBe("ST-abc123"); expect(result.clientNonce).toMatch(/^[0-9a-f]{40}$/); - expect(calls).toHaveLength(1); expect(calls[0].url).toBe( "https://vault.example.com:8200/v1/auth/oidc/oidc/auth_url", ); - expect(calls[0].init.method).toBe("POST"); - const headers = calls[0].init.headers as Record; - expect(headers["X-Vault-Namespace"]).toBe("team-a"); - const body = JSON.parse(calls[0].init.body as string); - expect(body).toMatchObject({ + expect(calls[0].init?.method).toBe("POST"); + expect(calls[0].init?.headers?.["X-Vault-Namespace"]).toBe("team-a"); + expect(JSON.parse(String(calls[0].init?.body))).toMatchObject({ role: "developer", - redirect_uri: "https://termix/vault/oidc/callback", + redirect_uri: "https://termix/plugin-api/vault/oidc/callback", + client_nonce: result.clientNonce, }); - expect(body.client_nonce).toBe(result.clientNonce); }); it("completeVaultOidc returns the client token", async () => { - const calls = mockFetch(() => ({ + const { fetch, calls } = fakeFetch(() => ({ body: { auth: { client_token: "hvs.TESTTOKEN" } }, })); - const token = await completeVaultOidc(profile, { + const token = await completeVaultOidc(fetch, profile, { state: "ST-abc123", code: "auth-code", clientNonce: "nonce123", @@ -192,17 +166,18 @@ describe("Vault HTTP flow (mocked fetch)", () => { expect(url.searchParams.get("state")).toBe("ST-abc123"); expect(url.searchParams.get("code")).toBe("auth-code"); expect(url.searchParams.get("client_nonce")).toBe("nonce123"); - expect(calls[0].init.method).toBe("GET"); + expect(calls[0].init?.method).toBe("GET"); }); it("signWithVault posts the public key and returns signed_key", async () => { - const calls = mockFetch(() => ({ + const { fetch, calls } = fakeFetch(() => ({ body: { data: { signed_key: "ssh-ed25519-cert-v01@openssh.com AAAAcert" }, }, })); const cert = await signWithVault( + fetch, profile, "hvs.TESTTOKEN", "ssh-ed25519 AAAApub comment", @@ -212,11 +187,8 @@ describe("Vault HTTP flow (mocked fetch)", () => { expect(calls[0].url).toBe( "https://vault.example.com:8200/v1/ssh-client-signer/sign/my-role", ); - const headers = calls[0].init.headers as Record; - expect(headers["X-Vault-Token"]).toBe("hvs.TESTTOKEN"); - expect(headers["X-Vault-Namespace"]).toBe("team-a"); - const body = JSON.parse(calls[0].init.body as string); - expect(body).toMatchObject({ + expect(calls[0].init?.headers?.["X-Vault-Token"]).toBe("hvs.TESTTOKEN"); + expect(JSON.parse(String(calls[0].init?.body))).toMatchObject({ public_key: "ssh-ed25519 AAAApub comment", cert_type: "user", valid_principals: "root,deploy", @@ -224,45 +196,21 @@ describe("Vault HTTP flow (mocked fetch)", () => { }); it("surfaces Vault error messages", async () => { - mockFetch(() => ({ + const { fetch } = fakeFetch(() => ({ status: 400, body: { errors: ["role not found", "permission denied"] }, })); - await expect( - signWithVault(profile, "tok", "ssh-ed25519 AAAA"), + signWithVault(fetch, profile, "tok", "ssh-ed25519 AAAA"), ).rejects.toThrow(/role not found; permission denied/); }); -}); -// Live integration against a real Vault (e.g. `vault server -dev` in Docker). -// Runs only when VAULT_ADDR + VAULT_TOKEN are set and the SSH signer mount -// (VAULT_SSH_MOUNT/VAULT_SSH_ROLE) has been configured by the test harness. -describe("Vault live signing", () => { - const addr = process.env.VAULT_ADDR; - const token = process.env.VAULT_TOKEN; - const run = !!addr && !!token; - - it.skipIf(!run)("signs an ephemeral key against a real Vault", async () => { - const profile: VaultProfileConfig = { - id: 99, - vaultAddr: addr!, - sshMount: process.env.VAULT_SSH_MOUNT || "ssh-client-signer", - sshRole: process.env.VAULT_SSH_ROLE || "my-role", - validPrincipals: "root", - keyType: "ssh-ed25519", + it("reports an unreachable Vault", async () => { + const fetch = async () => { + throw new Error("refused"); }; - - const pair = generateEphemeralKeyPair(profile.keyType); - const before = Math.floor(Date.now() / 1000); - const cert = await signWithVault(profile, token!, pair.publicKey); - - expect(cert).toMatch(/-cert-v01@openssh\.com /); - // The signed cert must parse with the same library used to connect. - const parsed = ssh2Utils.parseKey(cert); - expect(parsed instanceof Error).toBe(false); - - const validBefore = parseCertValidBefore(cert); - expect(validBefore).toBeGreaterThan(before); + await expect( + startVaultOidc(fetch, profile, "https://termix/cb"), + ).rejects.toThrow(/Failed to reach Vault/); }); }); diff --git a/plugins/vault/tests/backend/vault.test.ts b/plugins/vault/tests/backend/vault.test.ts new file mode 100644 index 000000000..8d76cb873 --- /dev/null +++ b/plugins/vault/tests/backend/vault.test.ts @@ -0,0 +1,471 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { PluginCapabilityError } from "@termix/plugin-sdk/backend"; +import { createMockCtx, createTestDb } from "@termix/plugin-sdk/testing"; +import { + HOST, + PROFILE, + SIGNED_CERT, + VAULT_ADDR, + createFakeVault, + env, + fakeSocket, + manifest, + pluginDir, + startServer, + type TestServer, +} from "./helpers.js"; + +const certs = vi.hoisted(() => ({ applyCertificateAuth: vi.fn() })); +vi.mock("@termix/plugin-sdk/ssh-certs", () => certs); + +let server: TestServer | null = null; + +beforeEach(() => { + certs.applyCertificateAuth.mockReset(); +}); + +afterEach(async () => { + await server?.close(); + server = null; +}); + +/** Creates a profile as user-1 and points host 7 at it. */ +async function profileOnHost(options: { shared?: boolean } = {}) { + const created = await server!.request("POST", "/profiles", { + body: { ...PROFILE, shared: options.shared ?? false }, + }); + expect(created.status).toBe(201); + await server!.mock.ctx.settings.setHost(7, "profileId", created.body.id); + return created.body as { id: number }; +} + +async function startSignIn(userId = "user-1") { + const socket = fakeSocket(); + await server!.provider().startInteraction!({ + userId, + hostId: 7, + host: { name: "box", ip: HOST.ip, username: HOST.username }, + socket: socket.socket, + requestOrigin: "https://termix.test", + payload: {}, + }); + return socket; +} + +describe("activate", () => { + it("registers the vault auth type, the sync entity and a public callback", async () => { + server = await startServer(); + const provider = server.provider(); + expect(provider).toMatchObject({ + type: "vault", + interaction: "vault", + needsUserInteraction: true, + supportsBackground: false, + }); + expect(server.mock.syncEntities).toEqual([ + expect.objectContaining({ type: "vaultProfiles", order: 20 }), + ]); + expect(server.mock.httpRouters[0]?.public).toEqual(["/oidc/callback"]); + }); + + // secrets:own and network:outbound are checked when used, ui:surface by the + // frontend. + it.each(["db:own", "network:serve", "auth:provide"])( + "fails closed without %s", + async (capability) => { + const db = await createTestDb(pluginDir); + const mock = createMockCtx({ + pluginId: manifest.id, + manifest, + capabilities: manifest.capabilities.filter((c) => c !== capability), + db: db.database, + router: () => ({ get: () => {}, use: () => {} }), + }); + const { activate } = await import("../../src/backend/index.js"); + await expect(activate(mock.ctx)).rejects.toThrow(PluginCapabilityError); + db.close(); + }, + ); +}); + +describe("profiles", () => { + it("creates, lists, updates and deletes a profile", async () => { + server = await startServer(); + const created = await server.request("POST", "/profiles", { + body: { ...PROFILE, vaultNamespace: " team ", tags: ["a", "b"] }, + }); + expect(created.status).toBe(201); + expect(created.body).toMatchObject({ + name: "Prod", + vaultAddr: VAULT_ADDR, + vaultNamespace: "team", + sshRole: "ops", + tags: ["a", "b"], + shared: false, + owned: true, + }); + + const listed = await server.request("GET", "/profiles"); + expect(listed.body).toHaveLength(1); + + const updated = await server.request( + "PUT", + `/profiles/${created.body.id}`, + { body: { name: "Staging", keyType: "ssh-rsa" } }, + ); + expect(updated.status).toBe(200); + expect(updated.body).toMatchObject({ name: "Staging", keyType: "ssh-rsa" }); + + const deleted = await server.request( + "DELETE", + `/profiles/${created.body.id}`, + ); + expect(deleted.status).toBe(200); + expect((await server.request("GET", "/profiles")).body).toEqual([]); + }); + + it("records a tombstone and drops cached certificates on delete", async () => { + server = await startServer(); + const { id } = await profileOnHost(); + server.db.sqlite + .prepare( + "INSERT INTO p_vault_tokens (user_id, profile_id, ssh_cert, private_key, expires_at) VALUES (?, ?, ?, ?, ?)", + ) + .run("user-1", id, "c", "k", "2999-01-01T00:00:00.000Z"); + const syncId = ( + server.db.sqlite + .prepare("SELECT sync_id FROM p_vault_profiles WHERE id = ?") + .get(id) as { sync_id: string } + ).sync_id; + + await server.request("DELETE", `/profiles/${id}`); + + expect(server.mock.tombstones).toEqual([ + { userId: "user-1", entityType: "vaultProfiles", syncId }, + ]); + expect( + server.db.sqlite + .prepare("SELECT COUNT(*) AS n FROM p_vault_tokens") + .get(), + ).toEqual({ n: 0 }); + }); + + it("requires name, address and signer role", async () => { + server = await startServer(); + const response = await server.request("POST", "/profiles", { + body: { name: "x" }, + }); + expect(response.status).toBe(400); + }); + + it("shows other users only shared profiles, and only the owner may change one", async () => { + server = await startServer(); + const own = await server.request("POST", "/profiles", { body: PROFILE }); + const shared = await server.request("POST", "/profiles", { + body: { ...PROFILE, name: "Shared", shared: true }, + }); + + const seen = await server.request("GET", "/profiles", { user: "user-2" }); + expect(seen.body.map((p: { name: string }) => p.name)).toEqual(["Shared"]); + expect(seen.body[0].owned).toBe(false); + + for (const id of [own.body.id, shared.body.id]) { + const put = await server.request("PUT", `/profiles/${id}`, { + user: "user-2", + body: { name: "mine" }, + }); + expect(put.status).toBe(403); + const del = await server.request("DELETE", `/profiles/${id}`, { + user: "user-2", + }); + expect(del.status).toBe(403); + } + }); + + it("needs the share permission to share a profile", async () => { + server = await startServer({ permissions: ["vault.use"] }); + const create = await server.request("POST", "/profiles", { + body: { ...PROFILE, shared: true }, + }); + expect(create.status).toBe(403); + + const own = await server.request("POST", "/profiles", { body: PROFILE }); + expect(own.status).toBe(201); + const share = await server.request("PUT", `/profiles/${own.body.id}`, { + body: { shared: true }, + }); + expect(share.status).toBe(403); + const unshare = await server.request("PUT", `/profiles/${own.body.id}`, { + body: { shared: false }, + }); + expect(unshare.status).toBe(200); + }); + + it("refuses every profile route without the use permission", async () => { + server = await startServer({ permissions: [] }); + for (const [method, path] of [ + ["GET", "/profiles"], + ["POST", "/profiles"], + ["PUT", "/profiles/1"], + ["DELETE", "/profiles/1"], + ]) { + const response = await server.request(method, path, { + body: method === "GET" ? undefined : PROFILE, + }); + expect(response.status, `${method} ${path}`).toBe(403); + } + }); + + it("answers 404 for a profile that does not exist", async () => { + server = await startServer(); + expect( + (await server.request("PUT", "/profiles/99", { body: {} })).status, + ).toBe(404); + expect((await server.request("DELETE", "/profiles/99")).status).toBe(404); + expect((await server.request("DELETE", "/profiles/abc")).status).toBe(400); + }); +}); + +describe("signing a certificate", () => { + it("runs the OIDC flow against Vault and stores the certificate sealed", async () => { + const vault = createFakeVault(); + server = await startServer({ fetch: vault.fetch }); + const { id } = await profileOnHost(); + + const socket = await startSignIn(); + expect(socket.sent).toEqual([ + expect.objectContaining({ + type: "vault_auth_url", + hostId: 7, + requestId: vault.state, + url: expect.stringContaining("https://idp.test/authorize"), + }), + ]); + const authUrlCall = vault.calls[0]; + expect(JSON.parse(String(authUrlCall.init?.body))).toMatchObject({ + role: "dev", + redirect_uri: "https://termix.test/plugin-api/vault/oidc/callback", + }); + expect(authUrlCall.init?.allowPrivateHosts).toEqual(["vault.internal"]); + + const callback = await server.request( + "GET", + `/oidc/callback?state=${vault.state}&code=abc`, + { user: "" }, + ); + expect(callback.status).toBe(200); + expect(callback.text).toContain("Vault sign-in complete"); + expect(socket.sent.at(-1)).toMatchObject({ + type: "vault_completed", + hostId: 7, + }); + + const signCall = vault.calls.find((call) => call.url.includes("/sign/")); + expect(signCall?.url).toBe(`${VAULT_ADDR}/v1/ssh-client-signer/sign/ops`); + expect(signCall?.init?.headers?.["X-Vault-Token"]).toBe("hvs.token"); + + const row = server.db.sqlite + .prepare("SELECT * FROM p_vault_tokens WHERE profile_id = ?") + .get(id) as { ssh_cert: string; private_key: string }; + expect(row.ssh_cert).not.toBe(SIGNED_CERT); + expect(await server.mock.ctx.secrets.unseal(row.ssh_cert)).toBe( + SIGNED_CERT, + ); + expect(await server.mock.ctx.secrets.unseal(row.private_key)).toContain( + "OPENSSH PRIVATE KEY", + ); + + const outcome = await server.provider().prepare({}, HOST, env()); + expect(outcome).toEqual({ status: "ready" }); + expect(certs.applyCertificateAuth).toHaveBeenCalledWith( + {}, + {}, + expect.objectContaining({ certificate: SIGNED_CERT }), + "root", + ); + }); + + it("answers a second use of the same callback with an error", async () => { + const vault = createFakeVault(); + server = await startServer({ fetch: vault.fetch }); + await profileOnHost(); + await startSignIn(); + const path = `/oidc/callback?state=${vault.state}&code=abc`; + expect((await server.request("GET", path)).status).toBe(200); + expect((await server.request("GET", path)).status).toBe(400); + }); + + it("reports Vault refusing to sign", async () => { + const vault = createFakeVault({ signStatus: 403 }); + server = await startServer({ fetch: vault.fetch }); + await profileOnHost(); + const socket = await startSignIn(); + const callback = await server.request( + "GET", + `/oidc/callback?state=${vault.state}&code=abc`, + ); + expect(callback.status).toBe(400); + expect(socket.sent.at(-1)).toMatchObject({ + type: "vault_error", + error: expect.stringContaining("permission denied"), + }); + }); + + it("passes an identity provider error through", async () => { + server = await startServer(); + const callback = await server.request( + "GET", + "/oidc/callback?error=access_denied", + ); + expect(callback.status).toBe(400); + expect(callback.text).toContain("access_denied"); + }); + + it("sends the 2.8 redirect URI while legacyCallback is on", async () => { + const vault = createFakeVault(); + server = await startServer({ + fetch: vault.fetch, + settings: { legacyCallback: true }, + }); + await profileOnHost(); + await startSignIn(); + expect(JSON.parse(String(vault.calls[0].init?.body)).redirect_uri).toBe( + "https://termix.test/vault/oidc/callback", + ); + }); + + it("lets a pending sign-in be cancelled", async () => { + const vault = createFakeVault(); + server = await startServer({ fetch: vault.fetch }); + await profileOnHost(); + await startSignIn(); + await server.provider().cancelInteraction!({ userId: "user-1", hostId: 7 }); + const callback = await server.request( + "GET", + `/oidc/callback?state=${vault.state}&code=abc`, + ); + expect(callback.status).toBe(400); + }); + + it("refuses to start without a profile on the host", async () => { + server = await startServer(); + await expect(startSignIn()).rejects.toThrow(/No Vault signer profile/); + }); +}); + +describe("the vault provider", () => { + it("fails without a profile on the host", async () => { + server = await startServer(); + expect(await server.provider().prepare({}, HOST, env())).toMatchObject({ + status: "error", + message: "Host has no Vault signer profile configured", + }); + }); + + it("asks for a sign-in without a certificate", async () => { + server = await startServer(); + await profileOnHost(); + expect(await server.provider().prepare({}, HOST, env())).toEqual({ + status: "interaction-required", + interaction: "vault", + message: expect.any(String), + flag: "requiresVaultAuth", + }); + }); + + it("uses a profile another user shared with the host's owner", async () => { + server = await startServer(); + const shared = await server.request("POST", "/profiles", { + user: "user-2", + body: { ...PROFILE, shared: true }, + }); + await server.mock.ctx.settings.setHost(7, "profileId", shared.body.id); + expect((await server.provider().prepare({}, HOST, env())).status).toBe( + "interaction-required", + ); + }); + + it("drops a certificate 2.8 cached with the user's data key", async () => { + server = await startServer(); + const { id } = await profileOnHost(); + server.db.sqlite + .prepare( + "INSERT INTO p_vault_tokens (user_id, profile_id, ssh_cert, private_key, expires_at) VALUES (?, ?, ?, ?, ?)", + ) + .run( + "user-1", + id, + "v1:dek-cipher", + "v1:dek-cipher", + "2999-01-01T00:00:00.000Z", + ); + + const outcome = await server.provider().prepare({}, HOST, env()); + expect(outcome.status).toBe("interaction-required"); + expect( + server.db.sqlite + .prepare("SELECT COUNT(*) AS n FROM p_vault_tokens") + .get(), + ).toEqual({ n: 0 }); + }); + + it("forgets an expired certificate", async () => { + server = await startServer(); + const { id } = await profileOnHost(); + const seal = server.mock.ctx.secrets.seal; + server.db.sqlite + .prepare( + "INSERT INTO p_vault_tokens (user_id, profile_id, ssh_cert, private_key, expires_at) VALUES (?, ?, ?, ?, ?)", + ) + .run( + "user-1", + id, + await seal("c"), + await seal("k"), + "2000-01-01T00:00:00.000Z", + ); + expect((await server.provider().prepare({}, HOST, env())).status).toBe( + "interaction-required", + ); + expect( + server.db.sqlite + .prepare("SELECT COUNT(*) AS n FROM p_vault_tokens") + .get(), + ).toEqual({ n: 0 }); + }); + + it("drops the certificate when the host rejects it", async () => { + const vault = createFakeVault(); + server = await startServer({ fetch: vault.fetch }); + await profileOnHost(); + await startSignIn(); + await server.request("GET", `/oidc/callback?state=${vault.state}&code=abc`); + + const outcome = server.provider().onAuthFailed!(HOST, env(), { + error: new Error("All configured authentication methods failed"), + retries: 0, + canRetry: false, + methodNotAvailable: false, + }); + expect(outcome).toMatchObject({ status: "interaction-required" }); + await vi.waitFor(() => + expect( + server!.db.sqlite + .prepare("SELECT COUNT(*) AS n FROM p_vault_tokens") + .get(), + ).toEqual({ n: 0 }), + ); + }); + + it("ignores other auth failures", async () => { + server = await startServer(); + expect( + server.provider().onAuthFailed!(HOST, env(), { + error: new Error("connection reset"), + retries: 0, + canRetry: false, + methodNotAvailable: false, + }), + ).toBeUndefined(); + }); +}); diff --git a/plugins/vault/tests/frontend/activate.test.tsx b/plugins/vault/tests/frontend/activate.test.tsx new file mode 100644 index 000000000..3b1642d9f --- /dev/null +++ b/plugins/vault/tests/frontend/activate.test.tsx @@ -0,0 +1,268 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { + act, + fireEvent, + render, + screen, + waitFor, +} from "@testing-library/react"; +import { + renderWithApp, + type RenderedPluginApp, +} from "@termix/plugin-sdk/testing"; +import type { PluginManifest } from "@termix/plugin-sdk/manifest"; +import * as plugin from "../../src/frontend/index"; +import { VaultOverlay } from "../../src/frontend/VaultOverlay"; +import { VaultAuthEditor } from "../../src/frontend/VaultAuthEditor"; +import { redirectUri } from "../../src/frontend/RedirectUriSetting"; +import manifestJson from "../../manifest.json"; +import locales from "../../locales/en.json"; + +const stubs = vi.hoisted(() => ({ + permissions: new Set(["use", "share"]), + api: { + get: vi.fn(), + post: vi.fn(), + put: vi.fn(), + delete: vi.fn(), + patch: vi.fn(), + }, + toast: { success: vi.fn(), error: vi.fn(), info: vi.fn(), warning: vi.fn() }, +})); + +vi.mock("@termix/plugin-sdk/frontend", async (importOriginal) => ({ + ...(await importOriginal()), + useTranslation: () => ({ + t: (key: string, options?: Record) => + options?.name ? `${key}:${options.name}` : key, + }), + usePluginApi: () => stubs.api, + usePermission: (permission: string) => stubs.permissions.has(permission), + useToast: () => stubs.toast, +})); + +const manifest = manifestJson as unknown as PluginManifest; + +let rendered: RenderedPluginApp | null = null; + +beforeEach(() => { + stubs.permissions = new Set(["use", "share"]); + for (const fn of Object.values(stubs.api)) fn.mockReset(); + stubs.api.get.mockResolvedValue({ data: [] }); +}); + +afterEach(async () => { + await rendered?.deactivate(); + rendered = null; +}); + +describe("vault activate", () => { + it("registers the terminal overlay and the settings field, and removes them on deactivate", async () => { + rendered = await renderWithApp(plugin, { manifest, locales }); + expect(rendered.registered.settingsComponents()).toEqual(["redirectUri"]); + expect(rendered.registered.slot("terminal.overlay")).toEqual([ + "vault.signIn", + ]); + await rendered.deactivate(); + expect(rendered.registered.settingsComponents()).toEqual([]); + expect(rendered.registered.slot("terminal.overlay")).toEqual([]); + rendered = null; + }); +}); + +describe("redirectUri", () => { + it("names the plugin callback, or the 2.8 one", () => { + expect(redirectUri(false, "https://t.example/app/")).toBe( + "https://t.example/app/plugin-api/vault/oidc/callback", + ); + expect(redirectUri(true, "https://t.example/")).toBe( + "https://t.example/vault/oidc/callback", + ); + }); +}); + +describe("VaultAuthEditor", () => { + const profiles = [ + { + id: 3, + name: "Prod", + vaultAddr: "https://v", + sshRole: "r", + shared: false, + owned: true, + }, + { + id: 4, + name: "Team", + vaultAddr: "https://v", + sshRole: "r", + shared: true, + owned: false, + }, + ]; + + it("lists the profiles and stores the choice as the profileId host setting", async () => { + stubs.api.get.mockResolvedValue({ data: profiles }); + const setField = vi.fn(); + render( + , + ); + await waitFor(() => expect(screen.getByText("Prod")).toBeTruthy()); + expect(stubs.api.get).toHaveBeenCalledWith("/profiles"); + expect(screen.getByText("editor.sharedProfile:Team")).toBeTruthy(); + + fireEvent.change(screen.getByRole("combobox"), { target: { value: "4" } }); + expect(setField).toHaveBeenCalledWith("pluginSettings", { + other: { x: 1 }, + vault: { profileId: 4 }, + }); + }); + + it("shows the saved profile", async () => { + stubs.api.get.mockResolvedValue({ data: profiles }); + render( + , + ); + await waitFor(() => + expect((screen.getByRole("combobox") as HTMLSelectElement).value).toBe( + "3", + ), + ); + }); + + it("creates a profile from the manager, shared only with the share permission", async () => { + stubs.api.post.mockResolvedValue({ data: {} }); + render(); + fireEvent.click(screen.getByText("profiles.manage")); + fireEvent.click(screen.getByText("profiles.new")); + const inputs = screen.getAllByRole("textbox"); + fireEvent.change(inputs[0], { target: { value: "Prod" } }); + fireEvent.change(inputs[1], { target: { value: "https://vault:8200" } }); + fireEvent.change(inputs[6], { target: { value: "ops" } }); + fireEvent.click(screen.getByRole("checkbox")); + await act(async () => { + fireEvent.click(screen.getByText("profiles.create")); + }); + expect(stubs.api.post).toHaveBeenCalledWith( + "/profiles", + expect.objectContaining({ + name: "Prod", + vaultAddr: "https://vault:8200", + sshRole: "ops", + shared: true, + }), + ); + }); + + it("hides sharing without the share permission and managing without use", async () => { + stubs.permissions = new Set(["use"]); + const { unmount } = render( + , + ); + fireEvent.click(screen.getByText("profiles.manage")); + fireEvent.click(screen.getByText("profiles.new")); + expect(screen.queryByRole("checkbox")).toBeNull(); + unmount(); + + stubs.permissions = new Set(); + stubs.api.get.mockClear(); + render(); + expect(screen.queryByText("profiles.manage")).toBeNull(); + expect(stubs.api.get).not.toHaveBeenCalled(); + }); +}); + +describe("VaultOverlay", () => { + function setup() { + let listener: (message: { + type: string; + [key: string]: unknown; + }) => void = () => {}; + const props = { + host: { id: 7 }, + subscribe: vi.fn((next: typeof listener) => { + listener = next; + return () => {}; + }), + holdConnectTimeout: vi.fn(), + fail: vi.fn(), + disconnect: vi.fn(), + send: vi.fn(), + connectPayload: vi.fn(() => ({ hostId: 7, cols: 80, rows: 24 })), + }; + render(); + const emit = (message: { type: string; [key: string]: unknown }) => + act(() => listener(message)); + return { props, emit }; + } + + it("starts the sign-in once, then reports a second request as a failure", () => { + const { props, emit } = setup(); + emit({ type: "vault_auth_required", hostId: 7 }); + expect(props.send).toHaveBeenCalledWith("vault_start_auth", { hostId: 7 }); + expect(props.holdConnectTimeout).toHaveBeenCalledWith(true); + + emit({ type: "vault_auth_required", hostId: 7 }); + expect(props.fail).toHaveBeenCalledWith("dialog.failed"); + }); + + it("opens Vault's sign-in, waits, and reconnects once signed", () => { + const close = vi.fn(); + const open = vi + .spyOn(window, "open") + .mockReturnValue({ close, focus: vi.fn() } as unknown as Window); + const { props, emit } = setup(); + emit({ type: "vault_auth_required", hostId: 7 }); + emit({ + type: "vault_auth_url", + hostId: 7, + requestId: "st-1", + url: "https://idp.test/authorize", + }); + expect(open).toHaveBeenCalledWith( + "https://idp.test/authorize", + "termix-vault-oidc", + "width=540,height=720", + ); + expect(screen.getByText("dialog.description")).toBeTruthy(); + + emit({ type: "vault_completed", hostId: 7 }); + expect(close).toHaveBeenCalled(); + expect(props.send).toHaveBeenCalledWith("vault_auth_completed", { + hostId: 7, + cols: 80, + rows: 24, + }); + expect(screen.queryByText("dialog.title")).toBeNull(); + open.mockRestore(); + }); + + it("cancels a pending sign-in", () => { + const open = vi.spyOn(window, "open").mockReturnValue(null); + const { props, emit } = setup(); + emit({ + type: "vault_auth_url", + hostId: 7, + requestId: "st-2", + url: "https://idp.test/authorize", + }); + fireEvent.click(screen.getByText("common.cancel")); + expect(props.send).toHaveBeenCalledWith("vault_cancel", { + hostId: 7, + requestId: "st-2", + }); + open.mockRestore(); + }); + + it("shows Vault's error", () => { + const { emit } = setup(); + emit({ type: "vault_error", hostId: 7, error: "permission denied" }); + expect(screen.getByText("permission denied")).toBeTruthy(); + }); +}); diff --git a/plugins/vault/tsconfig.json b/plugins/vault/tsconfig.json new file mode 100644 index 000000000..7b35314c3 --- /dev/null +++ b/plugins/vault/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "@termix/plugin-sdk/tsconfig.plugin.json", + "include": ["src/**/*.ts", "src/**/*.tsx", "tests/**/*.ts", "tests/**/*.tsx"] +} diff --git a/plugins/vault/vitest.config.ts b/plugins/vault/vitest.config.ts new file mode 100644 index 000000000..31ba004fc --- /dev/null +++ b/plugins/vault/vitest.config.ts @@ -0,0 +1,3 @@ +import { pluginVitestConfig } from "@termix/plugin-sdk/vitest-preset"; + +export default pluginVitestConfig(import.meta.url); diff --git a/src/backend/auth/legacy-providers.ts b/src/backend/auth/legacy-providers.ts deleted file mode 100644 index cc68fee11..000000000 --- a/src/backend/auth/legacy-providers.ts +++ /dev/null @@ -1,132 +0,0 @@ -/** - * Auth methods that still live in core for 2.9.0, registered through the same - * interfaces a plugin uses. Phase C moves each one into its own plugin by - * relocating its block from here; D1 deletes this file once it is empty. - * - * Nothing outside this file should branch on these type names. - */ - -import { getErrorMessage } from "../utils/error-message.js"; -import { sshLogger } from "../utils/logger.js"; -import { applyCertificateAuth } from "@termix/plugin-sdk/ssh-certs"; -import { registerSshAuthProvider } from "../hosts/connect/auth-provider-registry.js"; -import type { - SshAuthProvider, - SshConnectHost, -} from "../hosts/connect/types.js"; - -const VAULT_REQUIRED_MESSAGE = - "Vault SSH signer authentication required. Please open a Terminal connection first."; - -const AUTH_FAILED_PATTERN = /All configured authentication methods failed/i; - -function vaultProfileId(host: SshConnectHost): number | undefined { - return host.vaultProfile?.id ?? undefined; -} - -const vaultProvider: SshAuthProvider = { - type: "vault", - pluginId: "core", - labelKey: "hosts.filterAuthVault", - needsUserInteraction: true, - interaction: "vault", - prepare: async (config, host, env) => { - const profileId = vaultProfileId(host); - if (!profileId) { - return { - status: "error", - code: "failed", - message: "Host has no Vault signer profile configured", - }; - } - const { getVaultCert } = await import("../hosts/vault-signer-auth.js"); - const cert = await getVaultCert(env.userId, profileId); - if (!cert) { - env.log( - "info", - "No valid Vault certificate found, requesting authentication", - ); - return { - status: "interaction-required", - interaction: "vault", - message: VAULT_REQUIRED_MESSAGE, - flag: "requiresVaultAuth", - }; - } - try { - await applyCertificateAuth( - config, - env.client, - { privateKey: cert.privateKey, certificate: cert.sshCert }, - host.username, - ); - } catch (error) { - return { - status: "error", - code: "failed", - message: - "Vault SSH signer authentication failed: " + getErrorMessage(error), - }; - } - env.log("info", "Using cached Vault-signed certificate"); - return { status: "ready" }; - }, - onAuthFailed: (host, env, context) => { - if (!AUTH_FAILED_PATTERN.test(context.error.message)) return; - sshLogger.warn("Vault certificate authentication failed", { - operation: "vault_auth_failed", - hostId: env.hostId, - userId: env.userId, - }); - const profileId = vaultProfileId(host); - if (profileId) { - void import("../hosts/vault-signer-auth.js") - .then(({ deleteVaultCert }) => deleteVaultCert(env.userId, profileId)) - .catch(() => {}); - } - return { - status: "interaction-required", - interaction: "vault", - message: - "Vault authentication failed or expired. Please authenticate again.", - }; - }, - startInteraction: async (request) => { - const { loadVaultProfileForHost, startVaultAuth } = - await import("../hosts/vault-oidc-auth.js"); - const profile = await loadVaultProfileForHost( - request.hostId, - request.userId, - ); - if (!profile) { - throw new Error("No Vault signer profile configured for this host"); - } - await startVaultAuth( - request.userId, - request.hostId, - profile, - request.socket, - request.requestOrigin, - ); - }, - cancelInteraction: async (request) => { - if (request.hostId === undefined) return; - const { cancelVaultAuthByHost } = - await import("../hosts/vault-oidc-auth.js"); - cancelVaultAuthByHost(request.userId, request.hostId); - }, -}; - -let sshRegistered = false; - -export function registerLegacySshAuthProviders(): void { - if (sshRegistered) return; - sshRegistered = true; - - registerSshAuthProvider(vaultProvider); -} - -/** Test helper. */ -export function resetLegacyProvidersForTests(): void { - sshRegistered = false; -} diff --git a/src/backend/auth/registry.ts b/src/backend/auth/registry.ts index 1a9fe371e..984dc4b4b 100644 --- a/src/backend/auth/registry.ts +++ b/src/backend/auth/registry.ts @@ -2,8 +2,8 @@ * Login methods and second factors, from core and from plugins. * * Password login is registered by core and always present. Everything else - * (OIDC, LDAP, passkeys, TOTP, ...) registers here, from a plugin through - * ctx.auth or, in 2.9.0, from legacy-providers.ts. + * (OIDC, LDAP, passkeys, TOTP, ...) registers here from a plugin through + * ctx.auth. */ import type { diff --git a/src/backend/database/database.ts b/src/backend/database/database.ts index 20ceeb578..bac004ed9 100644 --- a/src/backend/database/database.ts +++ b/src/backend/database/database.ts @@ -7,6 +7,7 @@ import multer from "multer"; import cookieParser from "cookie-parser"; import userRoutes from "./routes/users.js"; import hostRoutes from "./routes/host.js"; +import { registerVaultCompatRoutes } from "./routes/host-compat-routes.js"; import alertRoutes from "./routes/alerts.js"; import credentialsRoutes from "./routes/credentials.js"; import sshAuthRoutes from "./routes/ssh-auth-routes.js"; @@ -18,7 +19,6 @@ import credentialSidebarPreferencesRoutes from "./routes/credential-sidebar-pref import uiPreferencesRoutes from "./routes/ui-preferences.js"; import termixIdRoutes from "./routes/termix-id.js"; import { registerAuditLogRoutes } from "./routes/audit-log-routes.js"; -import vaultRoutes from "./routes/vault.js"; import notificationChannelsRoutes from "./routes/notification-channels-routes.js"; import syncRoutes from "./routes/sync.js"; import dashboardRoutes from "./routes/dashboard-routes.js"; @@ -1795,7 +1795,9 @@ app.use("/credential-sidebar/preferences", credentialSidebarPreferencesRoutes); app.use("/ui-preferences", uiPreferencesRoutes); app.use("/termix-id", termixIdRoutes); registerAuditLogRoutes(app, authenticateJWT); -app.use("/vault", vaultRoutes); +const vaultCompatRoutes = express.Router(); +registerVaultCompatRoutes(vaultCompatRoutes); +app.use("/vault", vaultCompatRoutes); app.use("/", notificationChannelsRoutes); app.use("/sync", syncRoutes); app.use("/dashboard", dashboardRoutes); diff --git a/src/backend/database/db/index.ts b/src/backend/database/db/index.ts index ead5cf75e..45c81a065 100644 --- a/src/backend/database/db/index.ts +++ b/src/backend/database/db/index.ts @@ -978,11 +978,6 @@ const migrateSchema = () => { "override_credential_username", "INTEGER", ); - addColumnIfNotExists( - "ssh_data", - "vault_profile_id", - "INTEGER REFERENCES vault_profiles(id) ON DELETE SET NULL", - ); addColumnIfNotExists("ssh_data", "autostart_password", "TEXT"); addColumnIfNotExists("ssh_data", "autostart_key", "TEXT"); @@ -1579,67 +1574,6 @@ const migrateSchema = () => { }); } - try { - sqlite.prepare("SELECT id FROM vault_profiles LIMIT 1").get(); - } catch { - try { - sqlite.exec(` - CREATE TABLE IF NOT EXISTS vault_profiles ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id TEXT NOT NULL, - name TEXT NOT NULL, - description TEXT, - folder TEXT, - tags TEXT, - vault_addr TEXT NOT NULL, - vault_namespace TEXT, - oidc_mount TEXT, - oidc_role TEXT, - ssh_mount TEXT, - ssh_role TEXT NOT NULL, - valid_principals TEXT, - key_type TEXT, - shared 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 - ); - `); - } catch (createError) { - databaseLogger.warn("Failed to create vault_profiles table", { - operation: "schema_migration", - error: createError, - }); - } - } - - try { - sqlite.prepare("SELECT id FROM vault_tokens LIMIT 1").get(); - } catch { - try { - sqlite.exec(` - CREATE TABLE IF NOT EXISTS vault_tokens ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id TEXT NOT NULL, - profile_id INTEGER NOT NULL, - ssh_cert TEXT NOT NULL, - private_key TEXT NOT NULL, - created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, - expires_at TEXT NOT NULL, - last_used TEXT, - UNIQUE(user_id, profile_id), - FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE, - FOREIGN KEY (profile_id) REFERENCES vault_profiles (id) ON DELETE CASCADE - ); - `); - } catch (createError) { - databaseLogger.warn("Failed to create vault_tokens table", { - operation: "schema_migration", - error: createError, - }); - } - } - try { const existingRoles = sqlite.prepare("SELECT name, is_system FROM roles").all() as Array<{ name: string; is_system: number }>; @@ -1878,7 +1812,6 @@ const migrateSchema = () => { addColumnIfNotExists("ssh_folders", "sync_id", "TEXT"); addColumnIfNotExists("snippets", "sync_id", "TEXT"); addColumnIfNotExists("snippet_folders", "sync_id", "TEXT"); - addColumnIfNotExists("vault_profiles", "sync_id", "TEXT"); // Plugin runtime: lastError reports why a plugin is blocked or failed, and // grants record whether a capability came from an admin or from bundling. @@ -1896,7 +1829,6 @@ const migrateSchema = () => { "ssh_folders", "snippets", "snippet_folders", - "vault_profiles", ]; for (const table of syncIdTables) { diff --git a/src/backend/database/db/schema.mysql.ts b/src/backend/database/db/schema.mysql.ts index 605e2c68f..a44f321c2 100644 --- a/src/backend/database/db/schema.mysql.ts +++ b/src/backend/database/db/schema.mysql.ts @@ -194,13 +194,6 @@ export const hosts = mysqlTable( credentialId: int("credential_id").references(() => sshCredentials.id, { onDelete: "set null" }), overrideCredentialUsername: boolean("override_credential_username"), - // When authType is "vault", the host authenticates via a Vault SSH signer - // profile (shared settings, no secrets). The signing certificate is obtained - // per-user at connect time via an interactive Vault OIDC flow. - vaultProfileId: int("vault_profile_id").references( - () => vaultProfiles.id, - { onDelete: "set null" }, - ), enableTerminal: boolean("enable_terminal") .notNull() .default(true), @@ -660,71 +653,6 @@ export const auditLogs = mysqlTable( ], ); -// Vault SSH signer profiles. These hold ONLY non-secret connection settings and -// are intended to be shared across users (shared === true makes a profile -// visible to every user on the server). Each user authenticates to Vault via an -// interactive OIDC flow at connect time; no tokens or keys are stored here. -export const vaultProfiles = mysqlTable("vault_profiles", { - id: int("id").autoincrement().primaryKey(), - userId: varchar("user_id", { length: 255 }) - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - name: varchar("name", { length: 255 }).notNull(), - description: text("description"), - folder: varchar("folder", { length: 255 }), - tags: text("tags"), - // Vault server connection (non-secret) - vaultAddr: text("vault_addr").notNull(), - vaultNamespace: text("vault_namespace"), - // OIDC auth method mount + role used to obtain a Vault token interactively - oidcMount: text("oidc_mount"), - oidcRole: text("oidc_role"), - // SSH secrets engine mount + signer role used to sign the ephemeral key - sshMount: text("ssh_mount"), - sshRole: text("ssh_role").notNull(), - validPrincipals: text("valid_principals"), - // Ephemeral keypair algorithm to generate per connection - keyType: text("key_type"), - // When true the profile is visible/usable by all users on the server - shared: boolean("shared").notNull().default(false), - syncId: varchar("sync_id", { length: 255 }).unique(), - createdAt: text("created_at") - .notNull() - .default(sql`(CURRENT_TIMESTAMP)`), - updatedAt: text("updated_at") - .notNull() - .default(sql`(CURRENT_TIMESTAMP)`), -}); - -// Per-user cache of the ephemeral SSH private key + Vault-signed certificate. -// Transient: rows live only until the certificate expires. Secret fields are -// encrypted under the user's data-encryption key (see field-crypto.ts). -export const vaultTokens = mysqlTable( - "vault_tokens", - { - id: int("id").autoincrement().primaryKey(), - userId: varchar("user_id", { length: 255 }) - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - profileId: int("profile_id") - .notNull() - .references(() => vaultProfiles.id, { onDelete: "cascade" }), - - sshCert: text("ssh_cert").notNull(), - privateKey: text("private_key").notNull(), - - createdAt: text("created_at") - .notNull() - .default(sql`(CURRENT_TIMESTAMP)`), - expiresAt: varchar("expires_at", { length: 255 }).notNull(), - lastUsed: text("last_used"), - }, - // Declared inline in the production DDL as UNIQUE(...), but never here, - // so the generated Postgres and MySQL schemas allowed duplicates the - // SQLite deployment forbids — and the upsert had nothing to conflict on. - (table) => [uniqueIndex("idx_vault_tokens_user_profile").on(table.userId, table.profileId)], -); - export const apiKeys = mysqlTable( "api_keys", { diff --git a/src/backend/database/db/schema.pg.ts b/src/backend/database/db/schema.pg.ts index f37ad417c..f943c1b0c 100644 --- a/src/backend/database/db/schema.pg.ts +++ b/src/backend/database/db/schema.pg.ts @@ -195,13 +195,6 @@ export const hosts = pgTable( credentialId: integer("credential_id").references(() => sshCredentials.id, { onDelete: "set null" }), overrideCredentialUsername: boolean("override_credential_username"), - // When authType is "vault", the host authenticates via a Vault SSH signer - // profile (shared settings, no secrets). The signing certificate is obtained - // per-user at connect time via an interactive Vault OIDC flow. - vaultProfileId: integer("vault_profile_id").references( - () => vaultProfiles.id, - { onDelete: "set null" }, - ), enableTerminal: boolean("enable_terminal") .notNull() .default(true), @@ -661,71 +654,6 @@ export const auditLogs = pgTable( ], ); -// Vault SSH signer profiles. These hold ONLY non-secret connection settings and -// are intended to be shared across users (shared === true makes a profile -// visible to every user on the server). Each user authenticates to Vault via an -// interactive OIDC flow at connect time; no tokens or keys are stored here. -export const vaultProfiles = pgTable("vault_profiles", { - id: serial("id").primaryKey(), - userId: varchar("user_id", { length: 255 }) - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - name: varchar("name", { length: 255 }).notNull(), - description: text("description"), - folder: varchar("folder", { length: 255 }), - tags: text("tags"), - // Vault server connection (non-secret) - vaultAddr: text("vault_addr").notNull(), - vaultNamespace: text("vault_namespace"), - // OIDC auth method mount + role used to obtain a Vault token interactively - oidcMount: text("oidc_mount"), - oidcRole: text("oidc_role"), - // SSH secrets engine mount + signer role used to sign the ephemeral key - sshMount: text("ssh_mount"), - sshRole: text("ssh_role").notNull(), - validPrincipals: text("valid_principals"), - // Ephemeral keypair algorithm to generate per connection - keyType: text("key_type"), - // When true the profile is visible/usable by all users on the server - shared: boolean("shared").notNull().default(false), - syncId: varchar("sync_id", { length: 255 }).unique(), - createdAt: text("created_at") - .notNull() - .default(sql`CURRENT_TIMESTAMP`), - updatedAt: text("updated_at") - .notNull() - .default(sql`CURRENT_TIMESTAMP`), -}); - -// Per-user cache of the ephemeral SSH private key + Vault-signed certificate. -// Transient: rows live only until the certificate expires. Secret fields are -// encrypted under the user's data-encryption key (see field-crypto.ts). -export const vaultTokens = pgTable( - "vault_tokens", - { - id: serial("id").primaryKey(), - userId: varchar("user_id", { length: 255 }) - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - profileId: integer("profile_id") - .notNull() - .references(() => vaultProfiles.id, { onDelete: "cascade" }), - - sshCert: text("ssh_cert").notNull(), - privateKey: text("private_key").notNull(), - - createdAt: text("created_at") - .notNull() - .default(sql`CURRENT_TIMESTAMP`), - expiresAt: varchar("expires_at", { length: 255 }).notNull(), - lastUsed: text("last_used"), - }, - // Declared inline in the production DDL as UNIQUE(...), but never here, - // so the generated Postgres and MySQL schemas allowed duplicates the - // SQLite deployment forbids — and the upsert had nothing to conflict on. - (table) => [uniqueIndex("idx_vault_tokens_user_profile").on(table.userId, table.profileId)], -); - export const apiKeys = pgTable( "api_keys", { diff --git a/src/backend/database/db/schema.ts b/src/backend/database/db/schema.ts index dc283451d..518321254 100644 --- a/src/backend/database/db/schema.ts +++ b/src/backend/database/db/schema.ts @@ -187,13 +187,6 @@ export const hosts = sqliteTable( overrideCredentialUsername: integer("override_credential_username", { mode: "boolean", }), - // When authType is "vault", the host authenticates via a Vault SSH signer - // profile (shared settings, no secrets). The signing certificate is obtained - // per-user at connect time via an interactive Vault OIDC flow. - vaultProfileId: integer("vault_profile_id").references( - () => vaultProfiles.id, - { onDelete: "set null" }, - ), enableTerminal: integer("enable_terminal", { mode: "boolean" }) .notNull() .default(true), @@ -653,71 +646,6 @@ export const auditLogs = sqliteTable( ], ); -// Vault SSH signer profiles. These hold ONLY non-secret connection settings and -// are intended to be shared across users (shared === true makes a profile -// visible to every user on the server). Each user authenticates to Vault via an -// interactive OIDC flow at connect time; no tokens or keys are stored here. -export const vaultProfiles = sqliteTable("vault_profiles", { - id: integer("id").primaryKey({ autoIncrement: true }), - userId: text("user_id") - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - name: text("name").notNull(), - description: text("description"), - folder: text("folder"), - tags: text("tags"), - // Vault server connection (non-secret) - vaultAddr: text("vault_addr").notNull(), - vaultNamespace: text("vault_namespace"), - // OIDC auth method mount + role used to obtain a Vault token interactively - oidcMount: text("oidc_mount"), - oidcRole: text("oidc_role"), - // SSH secrets engine mount + signer role used to sign the ephemeral key - sshMount: text("ssh_mount"), - sshRole: text("ssh_role").notNull(), - validPrincipals: text("valid_principals"), - // Ephemeral keypair algorithm to generate per connection - keyType: text("key_type"), - // When true the profile is visible/usable by all users on the server - shared: integer("shared", { mode: "boolean" }).notNull().default(false), - syncId: text("sync_id").unique(), - createdAt: text("created_at") - .notNull() - .default(sql`CURRENT_TIMESTAMP`), - updatedAt: text("updated_at") - .notNull() - .default(sql`CURRENT_TIMESTAMP`), -}); - -// Per-user cache of the ephemeral SSH private key + Vault-signed certificate. -// Transient: rows live only until the certificate expires. Secret fields are -// encrypted under the user's data-encryption key (see field-crypto.ts). -export const vaultTokens = sqliteTable( - "vault_tokens", - { - id: integer("id").primaryKey({ autoIncrement: true }), - userId: text("user_id") - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - profileId: integer("profile_id") - .notNull() - .references(() => vaultProfiles.id, { onDelete: "cascade" }), - - sshCert: text("ssh_cert", { length: 8192 }).notNull(), - privateKey: text("private_key", { length: 8192 }).notNull(), - - createdAt: text("created_at") - .notNull() - .default(sql`CURRENT_TIMESTAMP`), - expiresAt: text("expires_at").notNull(), - lastUsed: text("last_used"), - }, - // Declared inline in the production DDL as UNIQUE(...), but never here, - // so the generated Postgres and MySQL schemas allowed duplicates the - // SQLite deployment forbids — and the upsert had nothing to conflict on. - (table) => [uniqueIndex("idx_vault_tokens_user_profile").on(table.userId, table.profileId)], -); - export const apiKeys = sqliteTable( "api_keys", { diff --git a/src/backend/database/repositories/factory.ts b/src/backend/database/repositories/factory.ts index ae3132ed5..ab69eff72 100644 --- a/src/backend/database/repositories/factory.ts +++ b/src/backend/database/repositories/factory.ts @@ -42,8 +42,6 @@ import { TrustedDeviceRepository } from "./trusted-device-repository.js"; import { UserDataExportRepository } from "./user-data-export-repository.js"; import { UserPreferenceRepository } from "./user-preference-repository.js"; import { UserRepository } from "./user-repository.js"; -import { VaultProfileRepository } from "./vault-profile-repository.js"; -import { VaultTokenRepository } from "./vault-token-repository.js"; /** * The context every repository runs against. @@ -416,20 +414,6 @@ export function createCurrentSharedCredentialSecretsRepository(): SharedCredenti ); } -export function createCurrentVaultProfileRepository(): VaultProfileRepository { - return new VaultProfileRepository( - createCurrentRepositoryContext(), - createCurrentRepositoryWriteHook("vault_profile_repository_write"), - ); -} - -export function createCurrentVaultTokenRepository(): VaultTokenRepository { - return new VaultTokenRepository( - createCurrentRepositoryContext(), - createCurrentRepositoryWriteHook("vault_token_repository_write"), - ); -} - /** * Loads the settings cache. Must run during startup on engines without a * synchronous read, before anything calls getCurrentSettingValue. diff --git a/src/backend/database/repositories/host-resolution-repository.ts b/src/backend/database/repositories/host-resolution-repository.ts index a93afafa4..4e120a4ab 100644 --- a/src/backend/database/repositories/host-resolution-repository.ts +++ b/src/backend/database/repositories/host-resolution-repository.ts @@ -18,7 +18,6 @@ export interface HostUpdateStateRecord { rdpCredentialId: number | null; vncCredentialId: number | null; telnetCredentialId: number | null; - vaultProfileId: number | null; authType: string; parentHostId: number | null; folder: string | null; @@ -146,7 +145,6 @@ export class HostResolutionRepository { rdpCredentialId: hosts.rdpCredentialId, vncCredentialId: hosts.vncCredentialId, telnetCredentialId: hosts.telnetCredentialId, - vaultProfileId: hosts.vaultProfileId, authType: hosts.authType, parentHostId: hosts.parentHostId, folder: hosts.folder, diff --git a/src/backend/database/repositories/vault-profile-repository.ts b/src/backend/database/repositories/vault-profile-repository.ts deleted file mode 100644 index bcf8cec56..000000000 --- a/src/backend/database/repositories/vault-profile-repository.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { desc, eq, or } from "drizzle-orm"; -import { randomUUID } from "crypto"; -import { vaultProfiles } from "../db/schema.js"; -import type { DatabaseContext } from "./database-context.js"; -import { rowsAffected } from "./mutation-result.js"; -import { - deleteReturning, - insertReturning, - updateReturning, -} from "./returning.js"; - -export type VaultProfileRecord = typeof vaultProfiles.$inferSelect; - -export interface VaultProfileCreateInput { - userId: string; - name: string; - description?: string | null; - folder?: string | null; - tags?: string | null; - vaultAddr: string; - vaultNamespace?: string | null; - oidcMount?: string | null; - oidcRole?: string | null; - sshMount?: string | null; - sshRole: string; - validPrincipals?: string | null; - keyType?: string | null; - shared?: boolean; -} - -export type VaultProfileUpdateInput = Partial< - Omit -> & { - updatedAt?: string; -}; - -export class VaultProfileRepository { - constructor( - private readonly context: DatabaseContext, - private readonly onWrite?: () => void | Promise, - ) {} - - async listVisibleToUser(userId: string): Promise { - return this.context.drizzle - .select() - .from(vaultProfiles) - .where( - or(eq(vaultProfiles.userId, userId), eq(vaultProfiles.shared, true)), - ) - .orderBy(desc(vaultProfiles.updatedAt)); - } - - async create(input: VaultProfileCreateInput): Promise { - const [created] = await insertReturning(this.context, vaultProfiles, { - syncId: randomUUID(), - userId: input.userId, - name: input.name, - description: input.description, - folder: input.folder, - tags: input.tags, - vaultAddr: input.vaultAddr, - vaultNamespace: input.vaultNamespace, - oidcMount: input.oidcMount, - oidcRole: input.oidcRole, - sshMount: input.sshMount, - sshRole: input.sshRole, - validPrincipals: input.validPrincipals, - keyType: input.keyType, - shared: input.shared ?? false, - }); - - await this.afterWrite(); - return created; - } - - async findById(id: number): Promise { - const rows = await this.context.drizzle - .select() - .from(vaultProfiles) - .where(eq(vaultProfiles.id, id)) - .limit(1); - - return rows[0] ?? null; - } - - async updateById( - id: number, - input: VaultProfileUpdateInput, - ): Promise { - const [updated] = await updateReturning( - this.context, - vaultProfiles, - { - ...input, - updatedAt: input.updatedAt ?? new Date().toISOString(), - }, - eq(vaultProfiles.id, id), - ); - - if (updated) { - await this.afterWrite(); - } - - return updated ?? null; - } - - async deleteById(id: number): Promise<{ syncId: string | null } | null> { - const rows = await deleteReturning( - this.context, - vaultProfiles, - eq(vaultProfiles.id, id), - ); - - if (rows.length === 0) return null; - await this.afterWrite(); - return { syncId: rows[0].syncId }; - } - - async deleteByUserId(userId: string): Promise { - const result = await this.context.drizzle - .delete(vaultProfiles) - .where(eq(vaultProfiles.userId, userId)); - - if (rowsAffected(result) > 0) { - await this.afterWrite(); - } - - return rowsAffected(result); - } - - private async afterWrite(): Promise { - await this.onWrite?.(); - } -} diff --git a/src/backend/database/repositories/vault-token-repository.ts b/src/backend/database/repositories/vault-token-repository.ts deleted file mode 100644 index a14ea70b7..000000000 --- a/src/backend/database/repositories/vault-token-repository.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { and, eq } from "drizzle-orm"; -import { vaultTokens } from "../db/schema.js"; -import type { DatabaseContext } from "./database-context.js"; -import { rowsAffected } from "./mutation-result.js"; -import { upsert } from "./returning.js"; - -export type VaultTokenRecord = typeof vaultTokens.$inferSelect; - -export interface VaultTokenUpsertInput { - userId: string; - profileId: number; - sshCert: string; - privateKey: string; - expiresAt: string; - createdAt?: string; -} - -export class VaultTokenRepository { - constructor( - private readonly context: DatabaseContext, - private readonly onWrite?: () => void | Promise, - ) {} - - async upsert(input: VaultTokenUpsertInput): Promise { - const createdAt = input.createdAt ?? new Date().toISOString(); - - await upsert( - this.context, - vaultTokens, - { - userId: input.userId, - profileId: input.profileId, - sshCert: input.sshCert, - privateKey: input.privateKey, - expiresAt: input.expiresAt, - }, - { - target: [vaultTokens.userId, vaultTokens.profileId], - set: { - sshCert: input.sshCert, - privateKey: input.privateKey, - expiresAt: input.expiresAt, - createdAt, - }, - }, - ); - - await this.afterWrite(); - } - - async findByUserAndProfile( - userId: string, - profileId: number, - ): Promise { - const rows = await this.context.drizzle - .select() - .from(vaultTokens) - .where( - and( - eq(vaultTokens.userId, userId), - eq(vaultTokens.profileId, profileId), - ), - ) - .limit(1); - - return rows[0] ?? null; - } - - async updateLastUsed( - userId: string, - profileId: number, - lastUsed = new Date().toISOString(), - ): Promise { - const result = await this.context.drizzle - .update(vaultTokens) - .set({ lastUsed }) - .where( - and( - eq(vaultTokens.userId, userId), - eq(vaultTokens.profileId, profileId), - ), - ); - - if (rowsAffected(result) > 0) { - await this.afterWrite(); - } - - return rowsAffected(result) > 0; - } - - async deleteByUserAndProfile( - userId: string, - profileId: number, - ): Promise { - const result = await this.context.drizzle - .delete(vaultTokens) - .where( - and( - eq(vaultTokens.userId, userId), - eq(vaultTokens.profileId, profileId), - ), - ); - - if (rowsAffected(result) > 0) { - await this.afterWrite(); - } - - return rowsAffected(result) > 0; - } - - async deleteByUserId(userId: string): Promise { - const result = await this.context.drizzle - .delete(vaultTokens) - .where(eq(vaultTokens.userId, userId)); - - if (rowsAffected(result) > 0) { - await this.afterWrite(); - } - - return rowsAffected(result); - } - - private async afterWrite(): Promise { - await this.onWrite?.(); - } -} diff --git a/src/backend/database/routes/delete-user-data.ts b/src/backend/database/routes/delete-user-data.ts index 5327c2d27..298cf48c4 100644 --- a/src/backend/database/routes/delete-user-data.ts +++ b/src/backend/database/routes/delete-user-data.ts @@ -23,10 +23,8 @@ import { createCurrentTrustedDeviceRepository, createCurrentUserPreferenceRepository, createCurrentUserRepository, - createCurrentVaultProfileRepository, createCurrentSharedCredentialSecretsRepository, createCurrentCredentialAccessRepository, - createCurrentVaultTokenRepository, } from "../repositories/factory.js"; export async function deleteUserAndRelatedData( @@ -96,11 +94,9 @@ export async function deleteUserAndRelatedData( // secret_sources cascade on the user's refUser() foreign key, as the // homepage and secret-sources plugins' adopted tables. The secret // source's token in ctx.secrets is cleaned up generically below, with - // every other plugin_settings row for this user. The opkssh plugin's - // cached certificates cascade the same way. + // every other plugin_settings row for this user. The opkssh and vault + // plugins' tables cascade the same way. - await createCurrentVaultTokenRepository().deleteByUserId(userId); - await createCurrentVaultProfileRepository().deleteByUserId(userId); await createCurrentTermixIdentityCaRepository().deleteByUserId(userId); await createCurrentTermixIdentityRepository().deleteByUserId(userId); await createCurrentOpenTabRepository().deleteByUserId(userId); diff --git a/src/backend/database/routes/host-compat-routes.ts b/src/backend/database/routes/host-compat-routes.ts index dfc808f04..e660c20fd 100644 --- a/src/backend/database/routes/host-compat-routes.ts +++ b/src/backend/database/routes/host-compat-routes.ts @@ -1,6 +1,7 @@ /** - * The 2.8 URLs under /host that something outside Termix still depends on: - * the OPKSSH and Step CA redirect URIs registered with identity providers. + * The 2.8 auth callback URLs something outside Termix still depends on: the + * OPKSSH and Step CA redirect URIs registered with identity providers, and + * the Vault one listed in Vault OIDC roles. * Each forwards to the plugin route that replaced it, so with the plugin off * it signs nobody in. */ @@ -50,3 +51,21 @@ export function registerHostAuthCompatRoutes(router: Router): void { res.redirect(307, localUrl(req, "/plugin-api/step-ca/callback")); }); } + +export function registerVaultCompatRoutes(router: Router): void { + /** + * @openapi + * /vault/oidc/callback: + * get: + * summary: Vault OIDC callback (2.8 URL) + * description: The redirect URI Vault OIDC roles allowed before 2.9. Redirects to /plugin-api/vault/oidc/callback with the same query, so existing Vault roles keep working. + * tags: + * - Vault + * responses: + * 307: + * description: Redirect to the vault plugin's callback. + */ + router.get("/oidc/callback", (req, res) => { + res.redirect(307, localUrl(req, "/plugin-api/vault/oidc/callback")); + }); +} diff --git a/src/backend/database/routes/host-normalizers.ts b/src/backend/database/routes/host-normalizers.ts index b5168a9a8..2a064de61 100644 --- a/src/backend/database/routes/host-normalizers.ts +++ b/src/backend/database/routes/host-normalizers.ts @@ -42,7 +42,6 @@ export const OWNER_PRIVATE_AUTH_FIELDS = { "authType", "authMethod", "credentialId", - "vaultProfileId", "overrideCredentialUsername", "shareSshAuth", "password", diff --git a/src/backend/database/routes/host.ts b/src/backend/database/routes/host.ts index a8c32ab4f..df10e3eb7 100644 --- a/src/backend/database/routes/host.ts +++ b/src/backend/database/routes/host.ts @@ -183,7 +183,6 @@ router.post( authType, shareSshAuth, credentialId, - vaultProfileId, key, keyPassword, keyType, @@ -305,8 +304,6 @@ router.post( authType: effectiveAuthType, shareSshAuth: shareSshAuth === true ? 1 : 0, credentialId: credentialId || null, - vaultProfileId: - effectiveAuthType === "vault" ? vaultProfileId || null : null, overrideCredentialUsername: overrideCredentialUsername ? 1 : 0, pin: pin ? 1 : 0, enableTerminal: enableTerminal ? 1 : 0, @@ -858,7 +855,6 @@ router.put( authType, shareSshAuth, credentialId, - vaultProfileId, key, keyPassword, keyType, @@ -981,8 +977,6 @@ router.put( authType: effectiveAuthType, shareSshAuth: shareSshAuth === true ? 1 : 0, credentialId: credentialId || null, - vaultProfileId: - effectiveAuthType === "vault" ? vaultProfileId || null : null, overrideCredentialUsername: overrideCredentialUsername ? 1 : 0, pin: pin ? 1 : 0, enableTerminal: enableTerminal ? 1 : 0, diff --git a/src/backend/database/routes/sync-entities.ts b/src/backend/database/routes/sync-entities.ts index f452f6fd8..5a5f9d947 100644 --- a/src/backend/database/routes/sync-entities.ts +++ b/src/backend/database/routes/sync-entities.ts @@ -15,7 +15,6 @@ import { hosts, sshCredentials, sshFolders, - vaultProfiles, userPreferences, } from "../db/schema.js"; import { @@ -47,11 +46,6 @@ const HOST_REFERENCES = [ syncField: "telnetCredentialSyncId", entityType: "sshCredentials", }, - { - field: "vaultProfileId", - syncField: "vaultProfileSyncId", - entityType: "vaultProfiles", - }, { field: "parentHostId", syncField: "parentHostSyncId", @@ -78,11 +72,8 @@ export function registerCoreSyncEntities(): void { order: 10, }); - registerEntity(CORE_OWNER, { - type: "vaultProfiles", - table: vaultProfiles, - order: 20, - }); + // vaultProfiles (order 20) moved to the vault plugin. Hosts no longer + // reference it: a host's profile is the plugin's profileId host setting. registerEntity(CORE_OWNER, { type: "sshFolders", diff --git a/src/backend/database/routes/sync-references.ts b/src/backend/database/routes/sync-references.ts index 843298c1e..b09f51a38 100644 --- a/src/backend/database/routes/sync-references.ts +++ b/src/backend/database/routes/sync-references.ts @@ -7,8 +7,7 @@ import { getEntity } from "../../plugins/sync-registry.js"; * A plain string rather than a union: the set is whatever is registered, and a * plugin can both register an entity and reference one. sync.ts resolves the * name through the registry, so an unknown one is an error there rather than a - * silent fallthrough - which is what the old three-way if/else did, routing - * anything that was not hosts or sshCredentials to vaultProfiles. + * silent fallthrough. */ export type SyncReferenceEntity = string; diff --git a/src/backend/database/routes/vault.ts b/src/backend/database/routes/vault.ts deleted file mode 100644 index 55e915c51..000000000 --- a/src/backend/database/routes/vault.ts +++ /dev/null @@ -1,448 +0,0 @@ -import express, { type Request, type Response } from "express"; -import { - createCurrentVaultProfileRepository, - createCurrentUserRepository, - createCurrentSyncTombstoneRepository, -} from "../repositories/factory.js"; -import type { VaultProfileUpdateInput } from "../repositories/vault-profile-repository.js"; -import type { AuthenticatedRequest } from "../../../types/index.js"; -import { authLogger } from "../../utils/logger.js"; -import { AuthManager } from "../../utils/auth-manager.js"; -import { completeVaultAuth } from "../../hosts/vault-oidc-auth.js"; - -const router = express.Router(); - -const authManager = AuthManager.getInstance(); -const authenticateJWT = authManager.createAuthMiddleware(); - -function isNonEmptyString(val: unknown): val is string { - return typeof val === "string" && val.trim().length > 0; -} - -async function userIsAdmin(userId: string): Promise { - try { - const user = await createCurrentUserRepository().findById(userId); - return !!user?.isAdmin; - } catch { - return false; - } -} - -function formatProfile( - row: Record, - currentUserId: string, -): Record { - return { - id: row.id, - name: row.name, - description: row.description, - folder: row.folder, - tags: - typeof row.tags === "string" - ? row.tags - ? (row.tags as string).split(",").filter(Boolean) - : [] - : [], - vaultAddr: row.vaultAddr, - vaultNamespace: row.vaultNamespace, - oidcMount: row.oidcMount, - oidcRole: row.oidcRole, - sshMount: row.sshMount, - sshRole: row.sshRole, - validPrincipals: row.validPrincipals, - keyType: row.keyType, - shared: !!row.shared, - owned: row.userId === currentUserId, - createdAt: row.createdAt, - updatedAt: row.updatedAt, - }; -} - -/** - * @openapi - * /vault/oidc/callback: - * get: - * summary: Vault OIDC callback - * description: Unauthenticated endpoint the IdP redirects to after login. Correlates the authorization code to a pending session via the Vault-issued state parameter. - * tags: - * - Vault - * parameters: - * - in: query - * name: state - * required: true - * schema: - * type: string - * - in: query - * name: code - * required: true - * schema: - * type: string - * - in: query - * name: error - * schema: - * type: string - * responses: - * 200: - * description: HTML page confirming sign-in success or failure. - * 400: - * description: Missing parameters or authentication failure. - */ -router.get("/oidc/callback", async (req: Request, res: Response) => { - const state = String(req.query.state || ""); - const code = String(req.query.code || ""); - const oidcError = req.query.error ? String(req.query.error) : ""; - - const esc = (value: string): string => - value - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); - - const html = (title: string, message: string) => - `${esc(title)} - -

${esc(title)}

${esc(message)}

-
`; - - if (oidcError) { - return res - .status(400) - .send(html("Vault sign-in failed", `Vault returned: ${oidcError}`)); - } - if (!state || !code) { - return res - .status(400) - .send(html("Vault sign-in failed", "Missing state or code.")); - } - - const result = await completeVaultAuth(state, code); - if (result.ok) { - return res.send( - html( - "Vault sign-in complete", - "You can close this window and return to Termix.", - ), - ); - } - return res - .status(400) - .send( - html("Vault sign-in failed", result.error || "Authentication failed."), - ); -}); - -/** - * @openapi - * /vault/profiles: - * get: - * summary: List Vault profiles - * description: Returns all Vault signer profiles owned by the authenticated user or marked as shared. - * tags: - * - Vault - * responses: - * 200: - * description: Array of Vault profile objects. - * 500: - * description: Failed to list vault profiles. - */ -router.get( - "/profiles", - authenticateJWT, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - try { - const rows = - await createCurrentVaultProfileRepository().listVisibleToUser(userId); - res.json( - rows.map((r) => formatProfile(r as Record, userId)), - ); - } catch (err) { - authLogger.error("Failed to list vault profiles", err); - res.status(500).json({ error: "Failed to list vault profiles" }); - } - }, -); - -/** - * @openapi - * /vault/profiles: - * post: - * summary: Create a Vault profile - * description: Creates a new Vault signer profile owned by the authenticated user. The shared flag requires admin privileges. - * tags: - * - Vault - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - name - * - vaultAddr - * - sshRole - * properties: - * name: - * type: string - * vaultAddr: - * type: string - * vaultNamespace: - * type: string - * oidcMount: - * type: string - * oidcRole: - * type: string - * sshMount: - * type: string - * sshRole: - * type: string - * validPrincipals: - * type: string - * keyType: - * type: string - * shared: - * type: boolean - * responses: - * 201: - * description: Created Vault profile object. - * 400: - * description: Missing required fields. - * 403: - * description: Non-admin attempted to create a shared profile. - * 500: - * description: Failed to create vault profile. - */ -router.post( - "/profiles", - authenticateJWT, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const { - name, - description, - folder, - tags, - vaultAddr, - vaultNamespace, - oidcMount, - oidcRole, - sshMount, - sshRole, - validPrincipals, - keyType, - shared, - } = req.body; - - if ( - !isNonEmptyString(name) || - !isNonEmptyString(vaultAddr) || - !isNonEmptyString(sshRole) - ) { - return res - .status(400) - .json({ error: "name, vaultAddr and sshRole are required" }); - } - - const wantShared = !!shared; - if (wantShared && !(await userIsAdmin(userId))) { - return res.status(403).json({ - error: "Only administrators can create shared Vault profiles", - }); - } - - try { - const inserted = await createCurrentVaultProfileRepository().create({ - userId, - name: name.trim(), - description: description?.trim() || null, - folder: folder?.trim() || null, - tags: Array.isArray(tags) ? tags.join(",") : tags || "", - vaultAddr: vaultAddr.trim(), - vaultNamespace: vaultNamespace?.trim() || null, - oidcMount: oidcMount?.trim() || null, - oidcRole: oidcRole?.trim() || null, - sshMount: sshMount?.trim() || null, - sshRole: sshRole.trim(), - validPrincipals: validPrincipals?.trim() || null, - keyType: keyType?.trim() || null, - shared: wantShared, - }); - res - .status(201) - .json(formatProfile(inserted as Record, userId)); - } catch (err) { - authLogger.error("Failed to create vault profile", err); - res.status(500).json({ error: "Failed to create vault profile" }); - } - }, -); - -/** - * @openapi - * /vault/profiles/{id}: - * put: - * summary: Update a Vault profile - * description: Updates a Vault signer profile. Only the owner may edit; toggling shared to true requires admin privileges. - * tags: - * - Vault - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * requestBody: - * content: - * application/json: - * schema: - * type: object - * responses: - * 200: - * description: Updated Vault profile object. - * 400: - * description: Invalid profile id. - * 403: - * description: Non-owner attempted to edit, or non-admin attempted to share. - * 404: - * description: Profile not found. - * 500: - * description: Failed to update vault profile. - */ -router.put( - "/profiles/:id", - authenticateJWT, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const id = parseInt(String(req.params.id), 10); - if (!Number.isFinite(id)) { - return res.status(400).json({ error: "Invalid profile id" }); - } - - try { - const repository = createCurrentVaultProfileRepository(); - const existing = await repository.findById(id); - if (!existing) { - return res.status(404).json({ error: "Profile not found" }); - } - if (existing.userId !== userId) { - return res - .status(403) - .json({ error: "Only the owner can edit this profile" }); - } - - const body = req.body; - const fields: VaultProfileUpdateInput = { - updatedAt: new Date().toISOString(), - }; - if (body.name !== undefined) fields.name = body.name?.trim(); - if (body.description !== undefined) - fields.description = body.description?.trim() || null; - if (body.folder !== undefined) - fields.folder = body.folder?.trim() || null; - if (body.tags !== undefined) - fields.tags = Array.isArray(body.tags) - ? body.tags.join(",") - : body.tags || ""; - if (body.vaultAddr !== undefined && isNonEmptyString(body.vaultAddr)) - fields.vaultAddr = body.vaultAddr.trim(); - if (body.vaultNamespace !== undefined) - fields.vaultNamespace = body.vaultNamespace?.trim() || null; - if (body.oidcMount !== undefined) - fields.oidcMount = body.oidcMount?.trim() || null; - if (body.oidcRole !== undefined) - fields.oidcRole = body.oidcRole?.trim() || null; - if (body.sshMount !== undefined) - fields.sshMount = body.sshMount?.trim() || null; - if (body.sshRole !== undefined && isNonEmptyString(body.sshRole)) - fields.sshRole = body.sshRole.trim(); - if (body.validPrincipals !== undefined) - fields.validPrincipals = body.validPrincipals?.trim() || null; - if (body.keyType !== undefined) - fields.keyType = body.keyType?.trim() || null; - if (body.shared !== undefined) { - if (!!body.shared && !(await userIsAdmin(userId))) { - return res.status(403).json({ - error: "Only administrators can share Vault profiles", - }); - } - fields.shared = !!body.shared; - } - - const updated = await repository.updateById(id, fields); - if (!updated) { - return res.status(404).json({ error: "Profile not found" }); - } - res.json(formatProfile(updated as Record, userId)); - } catch (err) { - authLogger.error("Failed to update vault profile", err); - res.status(500).json({ error: "Failed to update vault profile" }); - } - }, -); - -/** - * @openapi - * /vault/profiles/{id}: - * delete: - * summary: Delete a Vault profile - * description: Permanently deletes a Vault signer profile. Only the owner may delete it. - * tags: - * - Vault - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * responses: - * 200: - * description: Deletion confirmed. - * 400: - * description: Invalid profile id. - * 403: - * description: Non-owner attempted to delete. - * 404: - * description: Profile not found. - * 500: - * description: Failed to delete vault profile. - */ -router.delete( - "/profiles/:id", - authenticateJWT, - async (req: Request, res: Response) => { - const userId = (req as AuthenticatedRequest).userId; - const id = parseInt(String(req.params.id), 10); - if (!Number.isFinite(id)) { - return res.status(400).json({ error: "Invalid profile id" }); - } - try { - const repository = createCurrentVaultProfileRepository(); - const existing = await repository.findById(id); - if (!existing) { - return res.status(404).json({ error: "Profile not found" }); - } - if (existing.userId !== userId) { - return res - .status(403) - .json({ error: "Only the owner can delete this profile" }); - } - const deleted = await repository.deleteById(id); - if (deleted?.syncId) { - await createCurrentSyncTombstoneRepository().record( - userId, - "vaultProfiles", - deleted.syncId, - ); - } - res.json({ success: true }); - } catch (err) { - authLogger.error("Failed to delete vault profile", err); - res.status(500).json({ error: "Failed to delete vault profile" }); - } - }, -); - -export default router; diff --git a/src/backend/hosts/connect/auth-provider-registry.ts b/src/backend/hosts/connect/auth-provider-registry.ts index 168bc5d2e..c720e0797 100644 --- a/src/backend/hosts/connect/auth-provider-registry.ts +++ b/src/backend/hosts/connect/auth-provider-registry.ts @@ -2,8 +2,7 @@ * Every SSH auth type the server knows how to connect with. * * Core registers password, key, credential, agent and none. The rest come from - * plugins through ctx.auth.registerSshAuthProvider, or for 2.9.0 from - * src/backend/auth/legacy-providers.ts. A type with no provider fails the + * plugins through ctx.auth.registerSshAuthProvider. A type with no provider fails the * connect with a message naming the plugin that would provide it. */ diff --git a/src/backend/hosts/connect/core-providers.ts b/src/backend/hosts/connect/core-providers.ts index 42db2cafc..16e3807f6 100644 --- a/src/backend/hosts/connect/core-providers.ts +++ b/src/backend/hosts/connect/core-providers.ts @@ -1,4 +1,3 @@ -import { registerLegacySshAuthProviders } from "../../auth/legacy-providers.js"; import { registerBuiltinSshAuthProviders } from "./builtin-providers.js"; let registered = false; @@ -11,7 +10,6 @@ export function ensureCoreSshAuthProviders(): void { if (registered) return; registered = true; registerBuiltinSshAuthProviders(); - registerLegacySshAuthProviders(); } /** Test helper, pairs with resetSshAuthRegistryForTests. */ diff --git a/src/backend/hosts/connect/types.ts b/src/backend/hosts/connect/types.ts index 2601c7b22..50bc6bf47 100644 --- a/src/backend/hosts/connect/types.ts +++ b/src/backend/hosts/connect/types.ts @@ -41,7 +41,6 @@ export interface SshConnectHost { */ pluginSettings?: Record> | null; forceKeyboardInteractive?: boolean | null; - vaultProfile?: { id?: number | null } | null; terminalConfig?: Record | null; jumpHosts?: Array<{ hostId: number }> | null; useSocks5?: boolean | null; diff --git a/src/backend/hosts/file-manager/index.ts b/src/backend/hosts/file-manager/index.ts index d9d03998a..45dacdc7d 100644 --- a/src/backend/hosts/file-manager/index.ts +++ b/src/backend/hosts/file-manager/index.ts @@ -600,7 +600,6 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => { let resolvedSocks5Username = socks5Username; let resolvedSocks5Password = socks5Password; let resolvedSocks5ProxyChain = socks5ProxyChain; - let resolvedVaultProfileId: number | null = null; if (hostId && userId && !password && !sshKey) { try { const { resolveHostById, resolveHostBySyncId } = @@ -625,9 +624,6 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => { resolvedTerminalConfig = resolvedHost.terminalConfig as unknown as Record | undefined; resolvedScpLegacy = resolvedHost.scpLegacy ?? false; - resolvedVaultProfileId = - (resolvedHost.vaultProfile as { id?: number } | undefined)?.id ?? - null; if (resolvedHost.useSocks5) { resolvedUseSocks5 = resolvedHost.useSocks5; resolvedSocks5Host = resolvedHost.socks5Host; @@ -695,9 +691,6 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => { resolvedTerminalConfig = resolvedHost.terminalConfig as unknown as Record | undefined; resolvedScpLegacy = resolvedHost.scpLegacy ?? false; - resolvedVaultProfileId = - (resolvedHost.vaultProfile as { id?: number } | undefined)?.id ?? - null; if (resolvedHost.useSocks5) { resolvedUseSocks5 = resolvedHost.useSocks5; resolvedSocks5Host = resolvedHost.socks5Host; @@ -787,9 +780,6 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => { keyPassword: resolvedCredentials.keyPassword, certPublicKey: resolvedCredentials.certPublicKey, terminalConfig: resolvedTerminalConfig ?? null, - vaultProfile: resolvedVaultProfileId - ? { id: resolvedVaultProfileId } - : null, jumpHosts: resolvedJumpHosts, useSocks5: resolvedUseSocks5, socks5Host: resolvedSocks5Host, diff --git a/src/backend/hosts/host-resolver.ts b/src/backend/hosts/host-resolver.ts index b927eb210..92eff06af 100644 --- a/src/backend/hosts/host-resolver.ts +++ b/src/backend/hosts/host-resolver.ts @@ -3,7 +3,6 @@ import { findUsableCredential } from "./usable-credential.js"; import { resolveExternalSecretRefs } from "./external-secrets.js"; import { createCurrentHostResolutionRepository, - createCurrentVaultProfileRepository, createCurrentUserRepository, } from "../database/repositories/factory.js"; import type { HostResolutionHostRecord } from "../database/repositories/host-resolution-repository.js"; @@ -232,26 +231,6 @@ export async function resolveHostById( sharedAuthResolution === "recipient-override" ? userId : ownerId, ); - // Resolve a Vault SSH signer profile (shared settings, no secrets). The - // certificate itself is obtained per-user at connect time via Vault OIDC. - if (host.vaultProfileId && sharedAuthResolution !== "recipient-override") { - try { - const profile = await createCurrentVaultProfileRepository().findById( - host.vaultProfileId as number, - ); - if (profile) { - (host as Record).vaultProfile = profile; - host.authType = "vault"; - } - } catch (e) { - sshLogger.warn("Failed to resolve vault profile for host", { - operation: "host_resolver_vault_profile", - hostId, - error: getErrorMessage(e, "Unknown"), - }); - } - } - // Keyboard-interactive handlers run synchronously mid-handshake and can // only read their host settings from here. try { diff --git a/src/backend/hosts/vault-oidc-auth.ts b/src/backend/hosts/vault-oidc-auth.ts deleted file mode 100644 index ce10c385b..000000000 --- a/src/backend/hosts/vault-oidc-auth.ts +++ /dev/null @@ -1,214 +0,0 @@ -// Orchestrates the interactive Vault OIDC login that yields a signed, -// short-lived SSH certificate. Mirrors the OPKSSH session manager but is -// driven entirely over Vault's HTTP API (no external binary). -// -// Lifecycle: -// - startVaultAuth(): generate ephemeral keypair, ask Vault for an OIDC -// auth_url, stash a pending session keyed by the Vault-issued `state`, -// and send the auth_url to the browser over the terminal WebSocket. -// - The browser completes OIDC; the IdP redirects to VAULT_OIDC_CALLBACK_PATH. -// - completeVaultAuth(): exchange the code for a Vault token, sign the -// ephemeral key, cache the cert, and notify the browser to reconnect. - -import { WebSocket } from "ws"; -import { - createCurrentHostResolutionRepository, - createCurrentVaultProfileRepository, -} from "../database/repositories/factory.js"; -import { sshLogger } from "../utils/logger.js"; -import { - type VaultProfileConfig, - generateEphemeralKeyPair, - startVaultOidc, - completeVaultOidc, - signWithVault, - storeVaultCert, -} from "./vault-signer-auth.js"; - -export const VAULT_OIDC_CALLBACK_PATH = "/vault/oidc/callback"; - -const AUTH_TIMEOUT = 5 * 60 * 1000; - -interface VaultAuthSession { - state: string; - userId: string; - hostId: number; - profile: VaultProfileConfig; - clientNonce: string; - ephemeralPrivateKey: string; - ephemeralPublicKey: string; - ws: WebSocket; - timeout: NodeJS.Timeout; -} - -// Keyed by the Vault-issued OIDC `state` so the unauthenticated browser callback -// can correlate back to the originating session. -const sessions = new Map(); - -function rowToProfileConfig(row: Record): VaultProfileConfig { - return { - id: row.id as number, - vaultAddr: row.vaultAddr as string, - vaultNamespace: (row.vaultNamespace as string | null) ?? null, - oidcMount: (row.oidcMount as string | null) ?? null, - oidcRole: (row.oidcRole as string | null) ?? null, - sshMount: (row.sshMount as string | null) ?? null, - sshRole: row.sshRole as string, - validPrincipals: (row.validPrincipals as string | null) ?? null, - keyType: (row.keyType as string | null) ?? null, - }; -} - -/** Load the Vault profile referenced by a host, or null if not configured. */ -export async function loadVaultProfileForHost( - hostId: number, - userId: string, -): Promise { - const host = await createCurrentHostResolutionRepository().findHostById( - hostId, - userId, - ); - if (!host || host.vaultProfileId == null) return null; - - const profile = await createCurrentVaultProfileRepository().findById( - host.vaultProfileId as number, - ); - if (!profile) return null; - - return rowToProfileConfig(profile as Record); -} - -/** - * Begin an interactive Vault OIDC login and send the auth URL to the browser. - */ -export async function startVaultAuth( - userId: string, - hostId: number, - profile: VaultProfileConfig, - ws: WebSocket, - requestOrigin: string, -): Promise { - const ephemeral = generateEphemeralKeyPair(profile.keyType); - const redirectUri = `${requestOrigin}${VAULT_OIDC_CALLBACK_PATH}`; - - const { authUrl, state, clientNonce } = await startVaultOidc( - profile, - redirectUri, - ); - - const existing = sessions.get(state); - if (existing) clearTimeout(existing.timeout); - - const timeout = setTimeout(() => { - sessions.delete(state); - }, AUTH_TIMEOUT); - - sessions.set(state, { - state, - userId, - hostId, - profile, - clientNonce, - ephemeralPrivateKey: ephemeral.privateKey, - ephemeralPublicKey: ephemeral.publicKey, - ws, - timeout, - }); - - sshLogger.info("Started Vault OIDC auth session", { - operation: "vault_oidc_start", - userId, - hostId, - profileId: profile.id, - }); - - ws.send( - JSON.stringify({ - type: "vault_auth_url", - hostId, - url: authUrl, - }), - ); -} - -/** - * Complete a Vault OIDC login from the browser callback: exchange the code for a - * Vault token, sign the ephemeral key, cache the certificate, and notify the - * browser to resume the SSH connection. - */ -export async function completeVaultAuth( - state: string, - code: string, -): Promise<{ ok: boolean; error?: string }> { - const session = sessions.get(state); - if (!session) { - return { ok: false, error: "No matching Vault authentication session" }; - } - - try { - const token = await completeVaultOidc(session.profile, { - state, - code, - clientNonce: session.clientNonce, - }); - const signedCert = await signWithVault( - session.profile, - token, - session.ephemeralPublicKey, - ); - const expiresAt = await storeVaultCert( - session.userId, - session.profile.id, - session.ephemeralPrivateKey, - signedCert, - ); - - sshLogger.success("Completed Vault OIDC auth and cached certificate", { - operation: "vault_oidc_complete", - userId: session.userId, - hostId: session.hostId, - profileId: session.profile.id, - }); - - session.ws.send( - JSON.stringify({ - type: "vault_completed", - hostId: session.hostId, - expiresAt, - }), - ); - return { ok: true }; - } catch (e) { - const msg = e instanceof Error ? e.message : String(e); - sshLogger.error("Vault OIDC completion failed", e, { - operation: "vault_oidc_complete_error", - userId: session.userId, - hostId: session.hostId, - }); - try { - session.ws.send( - JSON.stringify({ - type: "vault_error", - hostId: session.hostId, - error: msg, - }), - ); - } catch { - // ws may be closed - } - return { ok: false, error: msg }; - } finally { - clearTimeout(session.timeout); - sessions.delete(state); - } -} - -/** Cancel a pending Vault auth session (e.g. user dismissed the dialog). */ -export function cancelVaultAuthByHost(userId: string, hostId: number): void { - for (const [state, s] of sessions) { - if (s.userId === userId && s.hostId === hostId) { - clearTimeout(s.timeout); - sessions.delete(state); - } - } -} diff --git a/src/backend/hosts/vault-signer-auth.ts b/src/backend/hosts/vault-signer-auth.ts deleted file mode 100644 index 05a092914..000000000 --- a/src/backend/hosts/vault-signer-auth.ts +++ /dev/null @@ -1,139 +0,0 @@ -// HashiCorp Vault SSH signer authentication. -// -// Flow (Vault-driven over HTTP): -// 1. Generate an ephemeral SSH keypair (never persisted long-term). -// 2. The user authenticates to Vault via an interactive OIDC flow -// (auth//oidc/auth_url -> browser -> auth//oidc/callback), -// yielding a short-lived Vault token. -// 3. Vault's SSH secrets engine signs the ephemeral public key -// (/sign/) -> short-lived OpenSSH certificate. -// 4. The ephemeral private key + certificate are cached per-user (encrypted) -// until the certificate expires, then used to connect via applyCertificateAuth. -// -// No Vault tokens, AppRole secrets, or long-lived private keys are ever stored. -// Vault connection SETTINGS live in shareable vault_profiles rows. -// -// The pure (DB-free) signing/OIDC/cert logic lives in vault-signer-core.ts and -// is re-exported here so callers have a single import surface. - -import { createCurrentVaultTokenRepository } from "../database/repositories/factory.js"; -import { DataCrypto } from "../utils/data-crypto.js"; -import { FieldCrypto } from "../utils/field-crypto.js"; -import { parseCertValidBefore } from "./vault-signer-core.js"; - -export type { - VaultProfileConfig, - EphemeralKeyPair, -} from "./vault-signer-core.js"; -export { - generateEphemeralKeyPair, - startVaultOidc, - completeVaultOidc, - signWithVault, - parseCertValidBefore, -} from "./vault-signer-core.js"; - -// Re-sign once we're within this many seconds of expiry. -const EXPIRY_SKEW_SECONDS = 60; - -function cacheRecordId(userId: string, profileId: number): string { - return `vault-${userId}-${profileId}`; -} - -/** - * Store an ephemeral private key + signed certificate for a user/profile. - * Returns the expiry as an ISO string. - */ -export async function storeVaultCert( - userId: string, - profileId: number, - privateKey: string, - signedCert: string, -): Promise { - const userDataKey = DataCrypto.getUserDataKey(userId); - if (!userDataKey) { - throw new Error("User data key not found"); - } - - const validBefore = parseCertValidBefore(signedCert); - const expiresMs = - validBefore > 0 ? validBefore * 1000 : Date.now() + 5 * 60 * 1000; - const expiresAt = new Date(expiresMs).toISOString(); - - const recordId = cacheRecordId(userId, profileId); - const encryptedCert = FieldCrypto.encryptField( - signedCert, - userDataKey, - recordId, - "ssh_cert", - ); - const encryptedKey = FieldCrypto.encryptField( - privateKey, - userDataKey, - recordId, - "private_key", - ); - - await createCurrentVaultTokenRepository().upsert({ - userId, - profileId, - sshCert: encryptedCert, - privateKey: encryptedKey, - expiresAt, - }); - - return expiresAt; -} - -/** - * Return a cached, still-valid ephemeral key + certificate for a user/profile, - * or null if none exists or it has (nearly) expired. - */ -export async function getVaultCert( - userId: string, - profileId: number, -): Promise<{ privateKey: string; sshCert: string } | null> { - const repository = createCurrentVaultTokenRepository(); - const row = await repository.findByUserAndProfile(userId, profileId); - - if (!row) return null; - - const expiresMs = new Date(row.expiresAt).getTime(); - if (expiresMs - EXPIRY_SKEW_SECONDS * 1000 < Date.now()) { - await deleteVaultCert(userId, profileId); - return null; - } - - const userDataKey = DataCrypto.getUserDataKey(userId); - if (!userDataKey) { - throw new Error("User data key not found"); - } - - const recordId = cacheRecordId(userId, profileId); - const sshCert = FieldCrypto.decryptField( - row.sshCert, - userDataKey, - recordId, - "ssh_cert", - ); - const privateKey = FieldCrypto.decryptField( - row.privateKey, - userDataKey, - recordId, - "private_key", - ); - - await repository.updateLastUsed(userId, profileId); - - return { privateKey, sshCert }; -} - -export async function deleteVaultCert( - userId: string, - profileId: number, -): Promise { - await createCurrentVaultTokenRepository().deleteByUserAndProfile( - userId, - profileId, - ); -} diff --git a/src/backend/starter.ts b/src/backend/starter.ts index bef231f20..d2453b6c8 100644 --- a/src/backend/starter.ts +++ b/src/backend/starter.ts @@ -381,6 +381,10 @@ async function provisionLocalDesktopUserIfNeeded(): Promise { await import("./utils/crypto-migration/step-ca-settings-migration.js"); await runStepCaSettingsMigration(); + const { runVaultSettingsMigration } = + await import("./utils/crypto-migration/vault-settings-migration.js"); + await runVaultSettingsMigration(); + const { runSecretSourcesTokenMigration } = await import("./utils/crypto-migration/secret-sources-token-migration.js"); await runSecretSourcesTokenMigration(); 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 4ef296d8f..653237504 100644 --- a/src/backend/tests/database/repositories/host-resolution-repository.test.ts +++ b/src/backend/tests/database/repositories/host-resolution-repository.test.ts @@ -195,7 +195,6 @@ describe("HostResolutionRepository", () => { rdpCredentialId: null, vncCredentialId: null, telnetCredentialId: null, - vaultProfileId: null, authType: "password", parentHostId: null, folder: null, diff --git a/src/backend/tests/database/repositories/vault-profile-repository.test.ts b/src/backend/tests/database/repositories/vault-profile-repository.test.ts deleted file mode 100644 index b5e9c4e2c..000000000 --- a/src/backend/tests/database/repositories/vault-profile-repository.test.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { afterEach, describe, expect, it } from "vitest"; -import { TestSqliteDatabase } from "./test-support.js"; -import { VaultProfileRepository } from "../../../database/repositories/vault-profile-repository.js"; - -describe("VaultProfileRepository", () => { - let adapter: TestSqliteDatabase | null = null; - - afterEach(async () => { - if (adapter) { - await adapter.close(); - adapter = null; - } - }); - - async function createRepository( - onWrite?: () => void | Promise, - ): Promise { - adapter = new TestSqliteDatabase(); - const context = await adapter.connect(); - await adapter.exec(` - INSERT INTO users (id, username, password_hash) - VALUES ('user-1', 'alice', 'hash'), ('user-2', 'bob', 'hash'); - INSERT INTO vault_profiles ( - id, user_id, name, vault_addr, ssh_role, shared, updated_at - ) - VALUES (1, 'user-1', 'owned', 'https://vault.one', 'role-one', 0, '2026-01-01T00:00:00.000Z'), - (2, 'user-2', 'shared', 'https://vault.two', 'role-two', 1, '2026-01-02T00:00:00.000Z'), - (3, 'user-2', 'hidden', 'https://vault.three', 'role-three', 0, '2026-01-03T00:00:00.000Z'); - `); - - return new VaultProfileRepository(context, onWrite); - } - - it("lists profiles owned by or shared with the user", async () => { - const repo = await createRepository(); - - const rows = await repo.listVisibleToUser("user-1"); - - expect(rows.map((row) => row.id)).toEqual([2, 1]); - }); - - it("creates and reads a profile", async () => { - let writeCount = 0; - const repo = await createRepository(() => { - writeCount += 1; - }); - - const created = await repo.create({ - userId: "user-1", - name: "new", - description: "desc", - folder: "folder", - tags: "prod,ssh", - vaultAddr: "https://vault.new", - vaultNamespace: "ns", - oidcMount: "oidc", - oidcRole: "oidc-role", - sshMount: "ssh", - sshRole: "ssh-role", - validPrincipals: "root", - keyType: "ed25519", - shared: true, - }); - - const found = await repo.findById(created.id); - expect(found).toMatchObject({ - userId: "user-1", - name: "new", - vaultAddr: "https://vault.new", - sshRole: "ssh-role", - shared: true, - }); - expect(writeCount).toBe(1); - }); - - it("updates and deletes profiles", async () => { - let writeCount = 0; - const repo = await createRepository(() => { - writeCount += 1; - }); - - const updated = await repo.updateById(1, { - name: "renamed", - shared: true, - updatedAt: "2026-02-01T00:00:00.000Z", - }); - expect(updated).toMatchObject({ - id: 1, - name: "renamed", - shared: true, - updatedAt: "2026-02-01T00:00:00.000Z", - }); - - expect(await repo.updateById(999, { name: "missing" })).toBeNull(); - expect(await repo.deleteById(1)).toEqual({ syncId: null }); - expect(await repo.deleteById(1)).toBeNull(); - expect(await repo.findById(1)).toBeNull(); - expect(writeCount).toBe(2); - }); - - it("deletes all profiles for a user", async () => { - let writeCount = 0; - const repo = await createRepository(() => { - writeCount += 1; - }); - - await expect(repo.deleteByUserId("user-2")).resolves.toBe(2); - await expect(repo.deleteByUserId("missing")).resolves.toBe(0); - - expect(await repo.findById(2)).toBeNull(); - expect(await repo.findById(3)).toBeNull(); - expect((await repo.findById(1))?.userId).toBe("user-1"); - expect(writeCount).toBe(1); - }); -}); diff --git a/src/backend/tests/database/repositories/vault-token-repository.test.ts b/src/backend/tests/database/repositories/vault-token-repository.test.ts deleted file mode 100644 index 01484f5be..000000000 --- a/src/backend/tests/database/repositories/vault-token-repository.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { afterEach, describe, expect, it } from "vitest"; -import { TestSqliteDatabase } from "./test-support.js"; -import { VaultTokenRepository } from "../../../database/repositories/vault-token-repository.js"; - -describe("VaultTokenRepository", () => { - let adapter: TestSqliteDatabase | null = null; - - afterEach(async () => { - if (adapter) { - await adapter.close(); - adapter = null; - } - }); - - async function createRepository( - onWrite?: () => void | Promise, - ): Promise { - adapter = new TestSqliteDatabase(); - const context = await adapter.connect(); - await adapter.exec(` - INSERT INTO users (id, username, password_hash) - VALUES ('user-1', 'alice', 'hash'), ('user-2', 'bob', 'hash'); - INSERT INTO vault_profiles (id, user_id, name, vault_addr, ssh_role) - VALUES (1, 'user-1', 'one', 'http://vault', 'r'), (2, 'user-2', 'two', 'http://vault', 'r'); - INSERT INTO vault_tokens ( - user_id, profile_id, ssh_cert, private_key, expires_at - ) - VALUES - ('user-1', 1, 'cert-1', 'key-1', '2099-01-01T00:00:00.000Z'), - ('user-2', 2, 'cert-2', 'key-2', '2099-01-01T00:00:00.000Z'); - `); - - return new VaultTokenRepository(context, onWrite); - } - - it("finds and upserts a token by user and profile", async () => { - let writeCount = 0; - const repo = await createRepository(() => { - writeCount += 1; - }); - - const existing = await repo.findByUserAndProfile("user-1", 1); - expect(existing?.sshCert).toBe("cert-1"); - - await repo.upsert({ - userId: "user-1", - profileId: 1, - sshCert: "new-cert", - privateKey: "new-key", - expiresAt: "2099-02-01T00:00:00.000Z", - createdAt: "2026-01-01T00:00:00.000Z", - }); - - const updated = await repo.findByUserAndProfile("user-1", 1); - expect(updated).toMatchObject({ - sshCert: "new-cert", - privateKey: "new-key", - expiresAt: "2099-02-01T00:00:00.000Z", - createdAt: "2026-01-01T00:00:00.000Z", - }); - expect(writeCount).toBe(1); - }); - - it("updates last-used and deletes one token", async () => { - let writeCount = 0; - const repo = await createRepository(() => { - writeCount += 1; - }); - - expect( - await repo.updateLastUsed("user-1", 1, "2026-01-02T00:00:00.000Z"), - ).toBe(true); - expect(await repo.updateLastUsed("missing", 1)).toBe(false); - expect((await repo.findByUserAndProfile("user-1", 1))?.lastUsed).toBe( - "2026-01-02T00:00:00.000Z", - ); - - expect(await repo.deleteByUserAndProfile("user-1", 1)).toBe(true); - expect(await repo.deleteByUserAndProfile("user-1", 1)).toBe(false); - expect(await repo.findByUserAndProfile("user-1", 1)).toBeNull(); - expect(await repo.findByUserAndProfile("user-2", 2)).not.toBeNull(); - expect(writeCount).toBe(2); - }); - - it("deletes all tokens for a user", async () => { - let writeCount = 0; - const repo = await createRepository(() => { - writeCount += 1; - }); - - await repo.upsert({ - userId: "user-1", - profileId: 2, - sshCert: "cert-extra", - privateKey: "key-extra", - expiresAt: "2099-03-01T00:00:00.000Z", - }); - - await expect(repo.deleteByUserId("user-1")).resolves.toBe(2); - await expect(repo.deleteByUserId("missing")).resolves.toBe(0); - - expect(await repo.findByUserAndProfile("user-1", 1)).toBeNull(); - expect(await repo.findByUserAndProfile("user-1", 2)).toBeNull(); - expect(await repo.findByUserAndProfile("user-2", 2)).not.toBeNull(); - expect(writeCount).toBe(2); - }); -}); diff --git a/src/backend/tests/database/routes/host-compat-routes.test.ts b/src/backend/tests/database/routes/host-compat-routes.test.ts index 6094be251..b86231792 100644 --- a/src/backend/tests/database/routes/host-compat-routes.test.ts +++ b/src/backend/tests/database/routes/host-compat-routes.test.ts @@ -1,5 +1,5 @@ /** - * The OPKSSH and Step CA redirect URIs identity providers were registered with before + * The OPKSSH, Step CA and Vault redirect URIs identity providers were registered with before * 2.9 must keep reaching the plugin's callback, query intact. */ @@ -62,3 +62,29 @@ describe("the old Step CA callback", () => { ); }); }); + +describe("the old Vault callback", () => { + it("forwards to the plugin's callback with the query", async () => { + const { registerVaultCompatRoutes } = + await import("../../../database/routes/host-compat-routes.js"); + const router = express.Router(); + registerVaultCompatRoutes(router); + const app = express(); + app.use("/vault", router); + const vaultServer = http.createServer(app); + await new Promise((resolve) => vaultServer.listen(0, resolve)); + const port = (vaultServer.address() as AddressInfo).port; + try { + const response = await fetch( + `http://127.0.0.1:${port}/vault/oidc/callback?code=abc&state=xyz`, + { redirect: "manual" }, + ); + expect(response.status).toBe(307); + expect(response.headers.get("location")).toBe( + "/plugin-api/vault/oidc/callback?code=abc&state=xyz", + ); + } finally { + await new Promise((resolve) => vaultServer.close(() => resolve())); + } + }); +}); diff --git a/src/backend/tests/database/routes/sync.test.ts b/src/backend/tests/database/routes/sync.test.ts index 7a843e704..15ebbca9b 100644 --- a/src/backend/tests/database/routes/sync.test.ts +++ b/src/backend/tests/database/routes/sync.test.ts @@ -33,7 +33,6 @@ describe("isValidEntityType", () => { "hosts", "sshCredentials", "sshFolders", - "vaultProfiles", "userPreferences", ]) { expect(isValidEntityType(type)).toBe(true); diff --git a/src/backend/tests/database/sync-references.test.ts b/src/backend/tests/database/sync-references.test.ts index 1450f4345..4a8f268da 100644 --- a/src/backend/tests/database/sync-references.test.ts +++ b/src/backend/tests/database/sync-references.test.ts @@ -23,7 +23,6 @@ describe("sync references", () => { rdpCredentialId: 13, vncCredentialId: null, telnetCredentialId: null, - vaultProfileId: 4, parentHostId: 3, }, async (entityType, id) => `${entityType}-${id}`, @@ -34,17 +33,14 @@ describe("sync references", () => { rdpCredentialSyncId: "sshCredentials-13", vncCredentialSyncId: null, telnetCredentialSyncId: null, - vaultProfileSyncId: "vaultProfiles-4", parentHostSyncId: "hosts-3", }); expect(row).not.toHaveProperty("credentialId"); - expect(row).not.toHaveProperty("vaultProfileId"); }); it("resolves stable sync IDs to IDs from the receiving database", async () => { const ids = new Map([ ["sshCredentials:credential-sync", 91], - ["vaultProfiles:vault-sync", 37], ["hosts:parent-sync", 52], ]); const row = await deserializeSyncReferences( @@ -55,7 +51,6 @@ describe("sync references", () => { rdpCredentialSyncId: null, vncCredentialSyncId: null, telnetCredentialSyncId: null, - vaultProfileSyncId: "vault-sync", parentHostSyncId: "parent-sync", }, async (entityType, syncId) => ids.get(`${entityType}:${syncId}`) ?? null, @@ -66,7 +61,6 @@ describe("sync references", () => { rdpCredentialId: null, vncCredentialId: null, telnetCredentialId: null, - vaultProfileId: 37, parentHostId: 52, }); expect(row).not.toHaveProperty("credentialSyncId"); diff --git a/src/backend/tests/hosts/connect/pipeline.test.ts b/src/backend/tests/hosts/connect/pipeline.test.ts index 002b6291f..839236af9 100644 --- a/src/backend/tests/hosts/connect/pipeline.test.ts +++ b/src/backend/tests/hosts/connect/pipeline.test.ts @@ -19,8 +19,7 @@ const mocks = vi.hoisted(() => ({ verifier: () => {}, createHostVerifier: vi.fn(), applyCertificateAuth: vi.fn(), - getVaultCert: vi.fn(), - deleteVaultCert: vi.fn(), + forgetCert: vi.fn(), applyAgentAuth: vi.fn(), performPortKnocking: vi.fn(), createJumpHostChain: vi.fn(), @@ -41,10 +40,6 @@ vi.mock("../../../hosts/host-key-verifier.js", () => ({ vi.mock("@termix/plugin-sdk/ssh-certs", () => ({ applyCertificateAuth: mocks.applyCertificateAuth, })); -vi.mock("../../../hosts/vault-signer-auth.js", () => ({ - getVaultCert: mocks.getVaultCert, - deleteVaultCert: mocks.deleteVaultCert, -})); vi.mock("../../../hosts/terminal-auth-helpers.js", () => ({ applyAgentAuth: mocks.applyAgentAuth, performPortKnocking: mocks.performPortKnocking, @@ -107,6 +102,39 @@ function host(overrides: Partial = {}): SshConnectHost { }; } +/** + * A plugin-style provider that needs a browser sign-in until it has a + * certificate, and forgets it when the host rejects it. + */ +function registerSignInProvider(hasCert: boolean) { + return registerSshAuthProvider({ + type: "signin", + pluginId: "fixture-plugin", + labelKey: "fixture.label", + needsUserInteraction: true, + interaction: "signin", + prepare: async (config) => { + if (!hasCert) { + return { + status: "interaction-required", + interaction: "signin", + message: "Sign in first", + }; + } + config.password = "cert"; + return { status: "ready" }; + }, + onAuthFailed: (_host, env) => { + mocks.forgetCert(env.userId, env.hostId); + return { + status: "interaction-required", + interaction: "signin", + message: "Sign in again", + }; + }, + }); +} + async function build( target: SshConnectHost, purpose: Parameters[1]["purpose"] = "terminal", @@ -286,30 +314,20 @@ describe("buildConnectConfig per auth type", () => { setSshAuthTypeOwnerSource(() => []); }); - it("vault needs a profile, then a cached certificate", async () => { - const noProfile = await build(host({ authType: "vault" })); - expect(noProfile.outcome).toMatchObject({ status: "error" }); + // Vault is the vault plugin's provider: see + // plugins/vault/tests/backend/vault.test.ts. - mocks.getVaultCert.mockResolvedValueOnce(null); - const noCert = await build( - host({ authType: "vault", vaultProfile: { id: 3 } }), - ); - // The file manager turns the flag into requiresVaultAuth in its 401. - expect(noCert.outcome).toMatchObject({ - status: "interaction-required", - interaction: "vault", - flag: "requiresVaultAuth", + it("a vault host without the vault plugin names it", async () => { + setSshAuthTypeOwnerSource(() => [ + { type: "vault", pluginId: "vault", pluginName: "HashiCorp Vault" }, + ]); + const { outcome } = await build(host({ authType: "vault" })); + expect(outcome).toEqual({ + status: "error", + code: "provider-missing", + message: "This host uses vault, which needs the HashiCorp Vault plugin", }); - - mocks.getVaultCert.mockResolvedValueOnce({ - privateKey: "k", - sshCert: "c", - }); - const ready = await build( - host({ authType: "vault", vaultProfile: { id: 3 } }), - ); - expect(ready.outcome.status).toBe("ready"); - expect(mocks.getVaultCert).toHaveBeenLastCalledWith("user-1", 3); + setSshAuthTypeOwnerSource(() => []); }); // Tailscale is a plugin-registered SSH auth provider now: see @@ -454,20 +472,19 @@ describe("connectHost", () => { }); it("throws the auth outcome instead of connecting", async () => { - mocks.getVaultCert.mockResolvedValue(null); - const vaultHost = () => - host({ authType: "vault", vaultProfile: { id: 3 } }); - const attempt = connectHost(vaultHost(), { + const dispose = registerSignInProvider(false); + const attempt = connectHost(host({ authType: "signin" }), { userId: "user-1", purpose: "tmux", }); await expect(attempt).rejects.toBeInstanceOf(SshConnectError); await expect( - connectHost(vaultHost(), { + connectHost(host({ authType: "signin" }), { userId: "user-1", purpose: "tmux", }).catch((error) => error.code), - ).resolves.toBe("vault-required"); + ).resolves.toBe("signin-required"); + dispose(); }); it("goes through the jump chain and forwards to the target", async () => { @@ -619,19 +636,17 @@ describe("connectHost", () => { it("lets the provider react to an auth failure", async () => { FakeSshClient.nextBehaviour = ["auth-fail"]; - mocks.getVaultCert.mockResolvedValueOnce({ - privateKey: "k", - sshCert: "c", - }); + const dispose = registerSignInProvider(true); await expect( - connectHost(host({ authType: "vault", vaultProfile: { id: 3 } }), { + connectHost(host({ authType: "signin" }), { userId: "user-1", purpose: "fleet", }), ).rejects.toThrow(/All configured authentication methods failed/); await vi.waitFor(() => - expect(mocks.deleteVaultCert).toHaveBeenCalledWith("user-1", 3), + expect(mocks.forgetCert).toHaveBeenCalledWith("user-1", 7), ); + dispose(); }); it("pools under the old key shape", async () => { diff --git a/src/backend/tests/hosts/host-resolver-sync-id.test.ts b/src/backend/tests/hosts/host-resolver-sync-id.test.ts index 9f0eeeedc..4d7d43aa2 100644 --- a/src/backend/tests/hosts/host-resolver-sync-id.test.ts +++ b/src/backend/tests/hosts/host-resolver-sync-id.test.ts @@ -11,7 +11,6 @@ vi.mock("../../database/repositories/factory.js", () => ({ findHostOwnerId, findHostById, }), - createCurrentVaultProfileRepository: () => ({}), createCurrentUserRepository: () => ({ findById: vi.fn() }), })); diff --git a/src/backend/tests/hosts/host-resolver.test.ts b/src/backend/tests/hosts/host-resolver.test.ts index 44ca995c1..8d49cd85f 100644 --- a/src/backend/tests/hosts/host-resolver.test.ts +++ b/src/backend/tests/hosts/host-resolver.test.ts @@ -6,7 +6,6 @@ const state = vi.hoisted(() => ({ isAdminBypass: false, overrideCredentialId: null as number | null, credentials: new Map>(), - vaultProfile: null as Record | null, auditCalls: [] as Record[], folderCredentialId: null as number | null, sharedSecret: null as Record | null, @@ -26,9 +25,6 @@ vi.mock("../../database/repositories/factory.js", () => ({ createCurrentSharedHostAuthOverrideRepository: () => ({ findCredentialId: async () => state.overrideCredentialId, }), - createCurrentVaultProfileRepository: () => ({ - findById: async () => state.vaultProfile, - }), createCurrentUserRepository: () => ({ findById: async (userId: string) => ({ id: userId, username: userId }), }), @@ -115,7 +111,6 @@ function baseHost(overrides: Record = {}) { keyType: null, credentialId: null, shareSshAuth: false, - vaultProfileId: null, sudoPassword: "owner-sudo", autostartPassword: "auto-pass", autostartKey: null, @@ -136,7 +131,6 @@ beforeEach(() => { state.isAdminBypass = false; state.overrideCredentialId = null; state.credentials.clear(); - state.vaultProfile = null; state.auditCalls = []; state.folderCredentialId = null; state.sharedSecret = null; @@ -372,13 +366,11 @@ describe("resolveHostById", () => { expect(host.key).toBe("RECIPIENT-KEY"); }); - it("fully replaces Vault authentication with the recipient override", async () => { + it("fully replaces a plugin auth type with the recipient override", async () => { state.host = baseHost({ authType: "vault", password: null, - vaultProfileId: 7, }); - state.vaultProfile = { id: 7 }; state.overrideCredentialId = 5; state.credentials.set("5:recipient", { id: 5, @@ -399,7 +391,6 @@ describe("resolveHostById", () => { expect(host.authType).toBe("key"); expect(host.key).toBe("RECIPIENT-KEY"); expect(host.certPublicKey).toBe("ssh-ed25519-cert-v01@example certificate"); - expect(host.vaultProfile).toBeUndefined(); }); it("falls back to the host username when the override credential has none", async () => { diff --git a/src/backend/tests/plugins/sync-registry.test.ts b/src/backend/tests/plugins/sync-registry.test.ts index f67104395..7ef5dc396 100644 --- a/src/backend/tests/plugins/sync-registry.test.ts +++ b/src/backend/tests/plugins/sync-registry.test.ts @@ -96,15 +96,16 @@ describe("core sync entities", () => { registerCoreSyncEntities(); }); - // The frozen array also lists networkTopology, snippets/snippetFolders and - // dashboardServiceLinks/homepageItems, now registered by the - // network-topology, snippets and homepage plugins rather than core. + // The frozen array also lists networkTopology, snippets/snippetFolders, + // dashboardServiceLinks/homepageItems and vaultProfiles, now registered by + // the network-topology, snippets, homepage and vault plugins rather than core. const PLUGIN_OWNED_TYPES = new Set([ "networkTopology", "snippets", "snippetFolders", "dashboardServiceLinks", "homepageItems", + "vaultProfiles", ]); it("registers every core-owned entity the Electron client knows about", () => { diff --git a/src/backend/tests/utils/vault-settings-migration.test.ts b/src/backend/tests/utils/vault-settings-migration.test.ts new file mode 100644 index 000000000..2632585f3 --- /dev/null +++ b/src/backend/tests/utils/vault-settings-migration.test.ts @@ -0,0 +1,152 @@ +/** + * 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 "../../utils/crypto-migration/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/utils/crypto-migration/vault-settings-migration.ts b/src/backend/utils/crypto-migration/vault-settings-migration.ts new file mode 100644 index 000000000..762e7d699 --- /dev/null +++ b/src/backend/utils/crypto-migration/vault-settings-migration.ts @@ -0,0 +1,149 @@ +/** + * 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 "../logger.js"; +import { + createCurrentPluginRepository, + createCurrentPluginSettingsRepository, +} from "../../database/repositories/factory.js"; +import { selectRows } from "./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/utils/field-crypto.ts b/src/backend/utils/field-crypto.ts index dc2ea890e..94eb6bff9 100644 --- a/src/backend/utils/field-crypto.ts +++ b/src/backend/utils/field-crypto.ts @@ -40,7 +40,6 @@ class FieldCrypto { // webhook URLs, which are credentials like any other. notification_channels: new Set(["config"]), termix_identity_ca: new Set(["privateKey"]), - vault_tokens: new Set(["sshCert", "privateKey"]), }; static encryptField( diff --git a/src/types/index.ts b/src/types/index.ts index a1478d329..42c71ec53 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -152,8 +152,6 @@ export type Host = { autostartKeyPassword?: string; credentialId?: number; - vaultProfileId?: number | null; - vaultProfile?: { id?: number | null }; overrideCredentialUsername?: boolean; userId?: string; enableTerminal: boolean; @@ -299,7 +297,6 @@ export interface HostData { keyType?: string; sudoPassword?: string; credentialId?: number | null; - vaultProfileId?: number | null; connectionOrigin?: "local" | "remote" | null; overrideCredentialUsername?: boolean; enableTerminal?: boolean; diff --git a/src/types/ui-types.ts b/src/types/ui-types.ts index 057ad41be..2904fdf5c 100644 --- a/src/types/ui-types.ts +++ b/src/types/ui-types.ts @@ -35,7 +35,6 @@ export type Host = { | "agent"; shareSshAuth?: boolean; credentialId?: string; - vaultProfileId?: string; overrideCredentialUsername?: boolean; password?: string; hasPassword?: boolean; @@ -200,26 +199,6 @@ export type Credential = { permissionLevel?: "use" | "manage"; }; -// HashiCorp Vault SSH signer profile — shareable connection settings only -// (no secrets). Users authenticate to Vault via OIDC at connect time. -export type VaultProfile = { - id: string; - name: string; - description?: string; - folder?: string; - tags?: string[]; - vaultAddr: string; - vaultNamespace?: string; - oidcMount?: string; - oidcRole?: string; - sshMount?: string; - sshRole: string; - validPrincipals?: string; - keyType?: string; - shared: boolean; - owned: boolean; -}; - export type HostFolder = { name: string; children: (Host | HostFolder)[]; diff --git a/src/ui/api/vault-profiles-api.ts b/src/ui/api/vault-profiles-api.ts deleted file mode 100644 index fbecea0e1..000000000 --- a/src/ui/api/vault-profiles-api.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { authApi, handleApiError } from "@/main-axios"; - -export interface VaultProfilePayload { - name: string; - description?: string | null; - folder?: string | null; - tags?: string[]; - vaultAddr: string; - vaultNamespace?: string | null; - oidcMount?: string | null; - oidcRole?: string | null; - sshMount?: string | null; - sshRole: string; - validPrincipals?: string | null; - keyType?: string | null; - shared?: boolean; -} - -export async function getVaultProfiles(): Promise[]> { - try { - const response = await authApi.get("/vault/profiles"); - return response.data; - } catch (error) { - throw handleApiError(error, "fetch vault profiles"); - } -} - -export async function createVaultProfile( - payload: VaultProfilePayload, -): Promise> { - try { - const response = await authApi.post("/vault/profiles", payload); - return response.data; - } catch (error) { - throw handleApiError(error, "create vault profile"); - } -} - -export async function updateVaultProfile( - id: number, - payload: Partial, -): Promise> { - try { - const response = await authApi.put(`/vault/profiles/${id}`, payload); - return response.data; - } catch (error) { - throw handleApiError(error, "update vault profile"); - } -} - -export async function deleteVaultProfile( - id: number, -): Promise> { - try { - const response = await authApi.delete(`/vault/profiles/${id}`); - return response.data; - } catch (error) { - throw handleApiError(error, "delete vault profile"); - } -} diff --git a/src/ui/locales/en.json b/src/ui/locales/en.json index a3412f169..8b66c2f9a 100644 --- a/src/ui/locales/en.json +++ b/src/ui/locales/en.json @@ -607,30 +607,6 @@ "authenticationMethodDesc": "Select the one method Termix will use to authenticate this SSH connection. Other methods are not tried automatically.", "storedCredential": "Stored Credential", "selectACredential": "Select a credential...", - "vaultProfile": "Vault Signer Profile", - "selectAVaultProfile": "Select a Vault profile...", - "vaultProfileHint": "Settings come from the shared profile; you'll sign in to Vault via OIDC when you connect. No secrets are stored.", - "vaultNewProfile": "New profile", - "vaultManageProfiles": "Manage Vault profiles", - "vaultAddrLabel": "Vault Address", - "vaultNamespaceLabel": "Namespace", - "vaultOidcMountLabel": "OIDC Auth Mount", - "vaultOidcRoleLabel": "OIDC Role", - "vaultSshMountLabel": "SSH Secrets Mount", - "vaultSshRoleLabel": "SSH Signer Role", - "vaultValidPrincipalsLabel": "Valid Principals", - "vaultKeyTypeLabel": "Ephemeral Key Type", - "vaultSharedLabel": "Share with all users", - "vaultCreateProfile": "Create profile", - "vaultProfileCreated": "Vault profile created", - "vaultProfileSaved": "Vault profile saved", - "vaultProfileDeleted": "Vault profile deleted", - "vaultProfileSaveFailed": "Failed to save Vault profile", - "vaultProfileDeleteFailed": "Failed to delete Vault profile", - "vaultSaveProfile": "Save profile", - "vaultProfileValidationError": "Name, Vault address and SSH signer role are required", - "vaultNoProfiles": "No Vault profiles yet.", - "vaultSharedBadge": "shared", "keyTypeLabel": "Key Type", "keyTypeAuto": "Auto Detect", "keyPasteTab": "Paste", diff --git a/src/ui/locales/translated/af_ZA.json b/src/ui/locales/translated/af_ZA.json index 0819a7c9f..ae138d2fd 100644 --- a/src/ui/locales/translated/af_ZA.json +++ b/src/ui/locales/translated/af_ZA.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "Kies die een metode wat Termix sal gebruik om hierdie SSH-verbinding te verifieer. Ander metodes word nie outomaties probeer nie.", "storedCredential": "Gestoorde geloofsbriewe", "selectACredential": "Kies 'n geloofsbrief...", - "vaultProfile": "Kluis Ondertekenaar Profiel", - "selectAVaultProfile": "Kies 'n kluisprofiel...", - "vaultProfileHint": "Instellings kom van die gedeelde profiel; jy sal by Vault aanmeld via OIDC wanneer jy koppel. Geen geheime word gestoor nie.", - "vaultNewProfile": "Nuwe profiel", "secretRefHint": "Jy kan 'n 1Password-verwysing (op://vault/item/field) in plaas van die geheim plak; dit word van jou geheime bron afgehaal wanneer jy koppel.", "secretSourcesManage": "Bestuur geheime bronne", "secretSourcesTitle": "Geheime bronne", @@ -739,26 +735,6 @@ "secretSourceRequired": "Naam, bediener-URL en token word vereis", "secretSourceSaved": "Geheime bron gestoor", "secretSourceDeleted": "Geheime bron verwyder", - "vaultManageProfiles": "Bestuur Vault-profiele", - "vaultAddrLabel": "Kluisadres", - "vaultNamespaceLabel": "Naamruimte", - "vaultOidcMountLabel": "OIDC-magtigingsmontering", - "vaultOidcRoleLabel": "OIDC-rol", - "vaultSshMountLabel": "SSH-geheime berg", - "vaultSshRoleLabel": "SSH-ondertekenaarrol", - "vaultValidPrincipalsLabel": "Geldige Prinsipale", - "vaultKeyTypeLabel": "Efemere Sleuteltipe", - "vaultSharedLabel": "Deel met alle gebruikers", - "vaultCreateProfile": "Skep profiel", - "vaultProfileCreated": "Kluisprofiel geskep", - "vaultProfileSaved": "Kluisprofiel gestoor", - "vaultProfileDeleted": "Kluisprofiel uitgevee", - "vaultProfileSaveFailed": "Kon nie kluisprofiel stoor nie", - "vaultProfileDeleteFailed": "Kon nie kluisprofiel uitvee nie", - "vaultSaveProfile": "Stoor profiel", - "vaultProfileValidationError": "Naam, kluisadres en SSH-ondertekenrol word vereis", - "vaultNoProfiles": "Geen Vault-profiele nog nie.", - "vaultSharedBadge": "gedeel", "keyTypeLabel": "Sleuteltipe", "keyTypeAuto": "Outomatiese opsporing", "keyPasteTab": "Plak", diff --git a/src/ui/locales/translated/ar_SA.json b/src/ui/locales/translated/ar_SA.json index 5a0c0ce59..80d62d3a6 100644 --- a/src/ui/locales/translated/ar_SA.json +++ b/src/ui/locales/translated/ar_SA.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "اختر الطريقة التي سيستخدمها Termix للتحقق من صحة اتصال SSH هذا. لن يتم تجربة الطرق الأخرى تلقائيًا.", "storedCredential": "بيانات اعتماد مخزنة", "selectACredential": "اختيار بيانات اعتماد...", - "vaultProfile": "ملف توقيع Vault", - "selectAVaultProfile": "اختيار ملف توقيع Vault...", - "vaultProfileHint": "تأتي الإعدادات من الملف المشترك؛ ستقوم بتسجيل الدخول إلى Vault عبر OIDC عند الاتصال. لا يتم تخزين أي أسرار.", - "vaultNewProfile": "ملف جديد", "secretRefHint": "يمكنك لصق مرجع 1Password (op://vault/item/field) بدلاً من السر؛ يتم جلبه من مصدر السر الخاص بك عند الاتصال.", "secretSourcesManage": "إدارة المصادر السرية", "secretSourcesTitle": "مصادر سرية", @@ -739,26 +735,6 @@ "secretSourceRequired": "الاسم وعنوان URL للخادم والرمز المميز مطلوبة", "secretSourceSaved": "تم حفظ المصدر السري", "secretSourceDeleted": "تم حذف المصدر السري", - "vaultManageProfiles": "إدارة ملفات Vault", - "vaultAddrLabel": "عنوان Vault", - "vaultNamespaceLabel": "مساحة الاسم", - "vaultOidcMountLabel": "تثبيت مصادقة OIDC", - "vaultOidcRoleLabel": "دور OIDC", - "vaultSshMountLabel": "تثبيت أسرار SSH", - "vaultSshRoleLabel": "دور موقع SSH", - "vaultValidPrincipalsLabel": "المستخدمون المسموح بهم", - "vaultKeyTypeLabel": "نوع المفتاح المؤقت", - "vaultSharedLabel": "مشاركة مع جميع المستخدمين", - "vaultCreateProfile": "إنشاء ملف تعريف", - "vaultProfileCreated": "تم إنشاء ملف تعريف Vault", - "vaultProfileSaved": "تم حفظ ملف تعريف Vault", - "vaultProfileDeleted": "تم حذف ملف تعريف Vault", - "vaultProfileSaveFailed": "فشل حفظ ملف تعريف Vault", - "vaultProfileDeleteFailed": "فشل حذف ملف تعريف Vault", - "vaultSaveProfile": "حفظ ملف التعريف", - "vaultProfileValidationError": "الاسم وعنوان Vault ودور الموقّع SSH مطلوب", - "vaultNoProfiles": "لا توجد ملفات تعريف Vault بعد.", - "vaultSharedBadge": "مشترك", "keyTypeLabel": "نوع المفتاح", "keyTypeAuto": "اكتشاف تلقائي", "keyPasteTab": "لصق", diff --git a/src/ui/locales/translated/bg_BG.json b/src/ui/locales/translated/bg_BG.json index a0e64506c..63b9aa33c 100644 --- a/src/ui/locales/translated/bg_BG.json +++ b/src/ui/locales/translated/bg_BG.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "Изберете единствения метод, който Termix ще използва за удостоверяване на тази SSH връзка. Други методи не се опитват автоматично.", "storedCredential": "Съхранени идентификационни данни", "selectACredential": "Изберете идентификационен номер...", - "vaultProfile": "Профил на подписващ в трезора", - "selectAVaultProfile": "Изберете профил в трезора...", - "vaultProfileHint": "Настройките идват от споделения профил; ще влезете в Vault чрез OIDC, когато се свържете. Не се съхраняват тайни.", - "vaultNewProfile": "Нов профил", "secretRefHint": "Можете да поставите препратка към 1Password (op://vault/item/field) вместо тайната; тя се извлича от вашия източник на тайна при свързване.", "secretSourcesManage": "Управлявайте тайните източници", "secretSourcesTitle": "Тайни източници", @@ -739,26 +735,6 @@ "secretSourceRequired": "Име, URL адрес на сървъра и токен са задължителни", "secretSourceSaved": "Секретният източник е запазен", "secretSourceDeleted": "Секретният източник е изтрит", - "vaultManageProfiles": "Управление на профили в хранилището", - "vaultAddrLabel": "Адрес на трезора", - "vaultNamespaceLabel": "Именно пространство", - "vaultOidcMountLabel": "OIDC Authentication Mount", - "vaultOidcRoleLabel": "Роля на OIDC", - "vaultSshMountLabel": "SSH Secrets Mount", - "vaultSshRoleLabel": "Роля на SSH подписващ", - "vaultValidPrincipalsLabel": "Валидни принципали", - "vaultKeyTypeLabel": "Тип ефимерен ключ", - "vaultSharedLabel": "Споделяне с всички потребители", - "vaultCreateProfile": "Създаване на профил", - "vaultProfileCreated": "Профилът на трезора е създаден", - "vaultProfileSaved": "Профилът на трезора е запазен", - "vaultProfileDeleted": "Профилът в трезора е изтрит", - "vaultProfileSaveFailed": "Запазването на профила в Трезора не бе успешно", - "vaultProfileDeleteFailed": "Изтриването на профила в Vault не бе успешно", - "vaultSaveProfile": "Запазване на профил", - "vaultProfileValidationError": "Име, адрес на трезора и роля на SSH подписващ са задължителни", - "vaultNoProfiles": "Все още няма профили в Трезора.", - "vaultSharedBadge": "споделено", "keyTypeLabel": "Тип ключ", "keyTypeAuto": "Автоматично откриване", "keyPasteTab": "Паста", diff --git a/src/ui/locales/translated/bn_BD.json b/src/ui/locales/translated/bn_BD.json index f8ef2b06f..287edcc2a 100644 --- a/src/ui/locales/translated/bn_BD.json +++ b/src/ui/locales/translated/bn_BD.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "এই SSH সংযোগটি প্রমাণীকরণের জন্য Termix যে পদ্ধতিটি ব্যবহার করবে, সেটি নির্বাচন করুন। অন্য পদ্ধতিগুলো স্বয়ংক্রিয়ভাবে চেষ্টা করা হয় না।", "storedCredential": "সংরক্ষিত পরিচয়পত্র", "selectACredential": "একটি শংসাপত্র নির্বাচন করুন...", - "vaultProfile": "ভল্ট স্বাক্ষরকারীর প্রোফাইল", - "selectAVaultProfile": "একটি ভল্ট প্রোফাইল নির্বাচন করুন...", - "vaultProfileHint": "সেটিংস শেয়ার করা প্রোফাইল থেকে আসে; সংযোগ করার সময় আপনি OIDC-এর মাধ্যমে ভল্টে সাইন ইন করবেন। কোনো গোপনীয় তথ্য সংরক্ষিত থাকে না।", - "vaultNewProfile": "নতুন প্রোফাইল", "secretRefHint": "আপনি সিক্রেটের পরিবর্তে একটি 1Password রেফারেন্স (op://vault/item/field) পেস্ট করতে পারেন; সংযোগ করার সময় এটি আপনার সিক্রেট সোর্স থেকে নিয়ে আসা হয়।", "secretSourcesManage": "গোপন উৎস পরিচালনা করুন", "secretSourcesTitle": "গোপন সূত্র", @@ -739,26 +735,6 @@ "secretSourceRequired": "নাম, সার্ভার ইউআরএল এবং টোকেন প্রয়োজন।", "secretSourceSaved": "গোপন উৎস সংরক্ষিত", "secretSourceDeleted": "গোপন সূত্র মুছে ফেলা হয়েছে", - "vaultManageProfiles": "ভল্ট প্রোফাইলগুলি পরিচালনা করুন", - "vaultAddrLabel": "ভল্ট ঠিকানা", - "vaultNamespaceLabel": "নেমস্পেস", - "vaultOidcMountLabel": "OIDC অথোরাইজেশন মাউন্ট", - "vaultOidcRoleLabel": "OIDC ভূমিকা", - "vaultSshMountLabel": "SSH সিক্রেটস মাউন্ট", - "vaultSshRoleLabel": "SSH স্বাক্ষরকারীর ভূমিকা", - "vaultValidPrincipalsLabel": "বৈধ অধ্যক্ষরা", - "vaultKeyTypeLabel": "ক্ষণস্থায়ী কী টাইপ", - "vaultSharedLabel": "সকল ব্যবহারকারীর সাথে শেয়ার করুন", - "vaultCreateProfile": "প্রোফাইল তৈরি করুন", - "vaultProfileCreated": "ভল্ট প্রোফাইল তৈরি করা হয়েছে", - "vaultProfileSaved": "ভল্ট প্রোফাইল সংরক্ষিত হয়েছে", - "vaultProfileDeleted": "ভল্ট প্রোফাইল মুছে ফেলা হয়েছে", - "vaultProfileSaveFailed": "ভল্ট প্রোফাইল সংরক্ষণ করতে ব্যর্থ হয়েছে", - "vaultProfileDeleteFailed": "ভল্ট প্রোফাইল মুছে ফেলা ব্যর্থ হয়েছে", - "vaultSaveProfile": "প্রোফাইল সংরক্ষণ করুন", - "vaultProfileValidationError": "নাম, ভল্ট অ্যাড্রেস এবং SSH সাইনার রোল প্রয়োজন।", - "vaultNoProfiles": "এখনো কোনো ভল্ট প্রোফাইল তৈরি হয়নি।", - "vaultSharedBadge": "শেয়ার করা", "keyTypeLabel": "চাবির ধরন", "keyTypeAuto": "স্বয়ংক্রিয়ভাবে সনাক্ত করুন", "keyPasteTab": "পেস্ট", diff --git a/src/ui/locales/translated/ca_ES.json b/src/ui/locales/translated/ca_ES.json index a7a8dfa10..8a95e0b0e 100644 --- a/src/ui/locales/translated/ca_ES.json +++ b/src/ui/locales/translated/ca_ES.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "Seleccioneu el mètode que Termix utilitzarà per autenticar aquesta connexió SSH. Els altres mètodes no es proven automàticament.", "storedCredential": "Credencial emmagatzemada", "selectACredential": "Selecciona una credencial...", - "vaultProfile": "Perfil del signant de la caixa forta", - "selectAVaultProfile": "Seleccioneu un perfil de Vault...", - "vaultProfileHint": "La configuració prové del perfil compartit; iniciaràs la sessió a Vault mitjançant OIDC quan et connectis. No s'emmagatzemen secrets.", - "vaultNewProfile": "Nou perfil", "secretRefHint": "Podeu enganxar una referència 1Password (op://vault/item/field) en comptes del secret; s'obté de la font del secret quan us connecteu.", "secretSourcesManage": "Gestiona fonts secretes", "secretSourcesTitle": "Fonts secretes", @@ -739,26 +735,6 @@ "secretSourceRequired": "El nom, l'URL del servidor i el testimoni són obligatoris", "secretSourceSaved": "Font secreta desada", "secretSourceDeleted": "Font secreta suprimida", - "vaultManageProfiles": "Gestiona els perfils de Vault", - "vaultAddrLabel": "Adreça de la caixa forta", - "vaultNamespaceLabel": "Espai de noms", - "vaultOidcMountLabel": "Muntatge d'autenticació OIDC", - "vaultOidcRoleLabel": "Rol de l'OIDC", - "vaultSshMountLabel": "Muntanya de Secrets SSH", - "vaultSshRoleLabel": "Rol de signant SSH", - "vaultValidPrincipalsLabel": "Principals vàlids", - "vaultKeyTypeLabel": "Tipus de clau efímera", - "vaultSharedLabel": "Comparteix amb tots els usuaris", - "vaultCreateProfile": "Crea un perfil", - "vaultProfileCreated": "Perfil de la caixa forta creat", - "vaultProfileSaved": "Perfil de la caixa forta desat", - "vaultProfileDeleted": "Perfil de la caixa forta suprimit", - "vaultProfileSaveFailed": "No s'ha pogut desar el perfil de Vault", - "vaultProfileDeleteFailed": "No s'ha pogut suprimir el perfil de Vault", - "vaultSaveProfile": "Desa el perfil", - "vaultProfileValidationError": "El nom, l'adreça de la caixa forta i el rol de signant SSH són obligatoris.", - "vaultNoProfiles": "Encara no hi ha perfils de Vault.", - "vaultSharedBadge": "compartit", "keyTypeLabel": "Tipus de clau", "keyTypeAuto": "Detecció automàtica", "keyPasteTab": "Enganxa", diff --git a/src/ui/locales/translated/cs_CZ.json b/src/ui/locales/translated/cs_CZ.json index d281c86f9..94c7833fa 100644 --- a/src/ui/locales/translated/cs_CZ.json +++ b/src/ui/locales/translated/cs_CZ.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "Vyberte jednu metodu, kterou Termix použije k ověření tohoto SSH připojení. Ostatní metody se nebudou automaticky zkoušet.", "storedCredential": "Uložené přihlašovací údaje", "selectACredential": "Vyberte pověření...", - "vaultProfile": "Profil podepisujícího trezoru", - "selectAVaultProfile": "Vyberte profil úložiště...", - "vaultProfileHint": "Nastavení pocházejí ze sdíleného profilu; po připojení se do úložiště přihlásíte přes OIDC. Žádné tajné kódy se neukládají.", - "vaultNewProfile": "Nový profil", "secretRefHint": "Místo tajného hesla můžete vložit odkaz na 1Password (op://vault/item/field); při připojování se načte ze zdroje vašeho tajného hesla.", "secretSourcesManage": "Správa tajných zdrojů", "secretSourcesTitle": "Tajné zdroje", @@ -739,26 +735,6 @@ "secretSourceRequired": "Jméno, URL adresa serveru a token jsou povinné", "secretSourceSaved": "Tajný zdroj uložen", "secretSourceDeleted": "Tajný zdroj smazán", - "vaultManageProfiles": "Správa profilů úložiště", - "vaultAddrLabel": "Adresa trezoru", - "vaultNamespaceLabel": "Jmenný prostor", - "vaultOidcMountLabel": "Připojení autorizace OIDC", - "vaultOidcRoleLabel": "Role OIDC", - "vaultSshMountLabel": "SSH tajemství Mount", - "vaultSshRoleLabel": "Role SSH podepisujícího", - "vaultValidPrincipalsLabel": "Platné principy", - "vaultKeyTypeLabel": "Typ dočasného klíče", - "vaultSharedLabel": "Sdílet se všemi uživateli", - "vaultCreateProfile": "Vytvořit profil", - "vaultProfileCreated": "Profil úložiště vytvořen", - "vaultProfileSaved": "Profil trezoru uložen", - "vaultProfileDeleted": "Profil trezoru smazán", - "vaultProfileSaveFailed": "Uložení profilu úložiště se nezdařilo.", - "vaultProfileDeleteFailed": "Profil v úložišti se nepodařilo smazat.", - "vaultSaveProfile": "Uložit profil", - "vaultProfileValidationError": "Jméno, adresa trezoru a role SSH signatáře jsou povinné.", - "vaultNoProfiles": "Zatím žádné profily v úložišti Vault.", - "vaultSharedBadge": "sdílené", "keyTypeLabel": "Typ klíče", "keyTypeAuto": "Automatická detekce", "keyPasteTab": "Pasta", diff --git a/src/ui/locales/translated/da_DK.json b/src/ui/locales/translated/da_DK.json index 3864d9f41..65eb97dc3 100644 --- a/src/ui/locales/translated/da_DK.json +++ b/src/ui/locales/translated/da_DK.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "Vælg den metode, som Termix vil bruge til at godkende denne SSH-forbindelse. Andre metoder afprøves ikke automatisk.", "storedCredential": "Gemte legitimationsoplysninger", "selectACredential": "Vælg en legitimationsoplysninger...", - "vaultProfile": "Profil for underskriver af arkiv", - "selectAVaultProfile": "Vælg en Vault-profil...", - "vaultProfileHint": "Indstillingerne kommer fra den delte profil; du logger ind på Vault via OIDC, når du opretter forbindelse. Der gemmes ingen hemmeligheder.", - "vaultNewProfile": "Ny profil", "secretRefHint": "Du kan indsætte en 1Password-reference (op://vault/item/field) i stedet for hemmeligheden; den hentes fra din hemmelige kilde, når du opretter forbindelse.", "secretSourcesManage": "Administrer hemmelige kilder", "secretSourcesTitle": "Hemmelige kilder", @@ -739,26 +735,6 @@ "secretSourceRequired": "Navn, server-URL og token er påkrævet", "secretSourceSaved": "Hemmelig kilde gemt", "secretSourceDeleted": "Hemmelig kilde slettet", - "vaultManageProfiles": "Administrer Vault-profiler", - "vaultAddrLabel": "Adresse i boksen", - "vaultNamespaceLabel": "Navnerum", - "vaultOidcMountLabel": "OIDC-godkendelsesmontering", - "vaultOidcRoleLabel": "OIDC-rolle", - "vaultSshMountLabel": "SSH-hemmeligheder monteres", - "vaultSshRoleLabel": "SSH-underskriverrolle", - "vaultValidPrincipalsLabel": "Gyldige principper", - "vaultKeyTypeLabel": "Kortvarig nøgletype", - "vaultSharedLabel": "Del med alle brugere", - "vaultCreateProfile": "Opret profil", - "vaultProfileCreated": "Vault-profil oprettet", - "vaultProfileSaved": "Vault-profil gemt", - "vaultProfileDeleted": "Vault-profil slettet", - "vaultProfileSaveFailed": "Kunne ikke gemme Vault-profilen", - "vaultProfileDeleteFailed": "Kunne ikke slette Vault-profilen", - "vaultSaveProfile": "Gem profil", - "vaultProfileValidationError": "Navn, Vault-adresse og SSH-underskriverrolle er påkrævet", - "vaultNoProfiles": "Ingen Vault-profiler endnu.", - "vaultSharedBadge": "delt", "keyTypeLabel": "Nøgletype", "keyTypeAuto": "Automatisk registrering", "keyPasteTab": "Indsæt", diff --git a/src/ui/locales/translated/de_DE.json b/src/ui/locales/translated/de_DE.json index c32988adf..ea825114d 100644 --- a/src/ui/locales/translated/de_DE.json +++ b/src/ui/locales/translated/de_DE.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "Wählen Sie die Methode aus, die Termix zur Authentifizierung dieser SSH-Verbindung verwenden soll. Andere Methoden werden nicht automatisch ausprobiert.", "storedCredential": "Gespeicherte Anmeldedaten", "selectACredential": "Anmeldedaten auswählen...", - "vaultProfile": "Vault-Signer-Profil", - "selectAVaultProfile": "Vault-Profil auswählen...", - "vaultProfileHint": "Die Einstellungen stammen aus dem freigegebenen Profil; Sie melden sich beim Verbinden per OIDC bei Vault an. Es werden keine Geheimnisse gespeichert.", - "vaultNewProfile": "Neues Profil", "secretRefHint": "Sie können anstelle des Geheimnisses eine 1Password-Referenz (op://vault/item/field) einfügen; diese wird beim Verbindungsaufbau aus Ihrer Geheimnisquelle abgerufen.", "secretSourcesManage": "Geheime Quellen verwalten", "secretSourcesTitle": "Geheime Quellen", @@ -739,26 +735,6 @@ "secretSourceRequired": "Name, Server-URL und Token werden benötigt.", "secretSourceSaved": "Geheime Quelle gerettet", "secretSourceDeleted": "Geheime Quelle gelöscht", - "vaultManageProfiles": "Vault-Profile verwalten", - "vaultAddrLabel": "Vault-Adresse", - "vaultNamespaceLabel": "Namespace", - "vaultOidcMountLabel": "OIDC-Auth-Mount", - "vaultOidcRoleLabel": "OIDC-Rolle", - "vaultSshMountLabel": "SSH-Secrets-Mount", - "vaultSshRoleLabel": "SSH-Signer-Rolle", - "vaultValidPrincipalsLabel": "Gültige Principals", - "vaultKeyTypeLabel": "Ephemerer Schlüsseltyp", - "vaultSharedLabel": "Mit allen Benutzern teilen", - "vaultCreateProfile": "Profil erstellen", - "vaultProfileCreated": "Vault-Profil erstellt", - "vaultProfileSaved": "Vault-Profil gespeichert", - "vaultProfileDeleted": "Vault-Profil gelöscht", - "vaultProfileSaveFailed": "Fehler beim Speichern des Vault-Profils", - "vaultProfileDeleteFailed": "Fehler beim Löschen des Vault-Profils", - "vaultSaveProfile": "Profil speichern", - "vaultProfileValidationError": "Name, Vault-Adresse und SSH-Signer-Rolle sind erforderlich", - "vaultNoProfiles": "Noch keine Vault-Profile.", - "vaultSharedBadge": "geteilt", "keyTypeLabel": "Schlüsseltyp", "keyTypeAuto": "Automatisch erkennen", "keyPasteTab": "Einfügen", diff --git a/src/ui/locales/translated/el_GR.json b/src/ui/locales/translated/el_GR.json index 7e5672961..34962f00d 100644 --- a/src/ui/locales/translated/el_GR.json +++ b/src/ui/locales/translated/el_GR.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "Επιλέξτε τη μέθοδο που θα χρησιμοποιήσει το Termix για τον έλεγχο ταυτότητας αυτής της σύνδεσης SSH. Άλλες μέθοδοι δεν δοκιμάζονται αυτόματα.", "storedCredential": "Αποθηκευμένα διαπιστευτήρια", "selectACredential": "Επιλέξτε ένα πιστοποιητικό...", - "vaultProfile": "Προφίλ Υπογράφοντος Vault", - "selectAVaultProfile": "Επιλέξτε ένα προφίλ Vault...", - "vaultProfileHint": "Οι ρυθμίσεις προέρχονται από το κοινόχρηστο προφίλ. Θα συνδεθείτε στο Vault μέσω OIDC όταν συνδεθείτε. Δεν αποθηκεύονται μυστικά.", - "vaultNewProfile": "Νέο προφίλ", "secretRefHint": "Μπορείτε να επικολλήσετε μια αναφορά 1Password (op://vault/item/field) αντί για το μυστικό. Αυτό ανακτάται από την μυστική σας πηγή κατά τη σύνδεση.", "secretSourcesManage": "Διαχείριση μυστικών πηγών", "secretSourcesTitle": "Μυστικές πηγές", @@ -739,26 +735,6 @@ "secretSourceRequired": "Απαιτούνται όνομα, διεύθυνση URL διακομιστή και διακριτικό", "secretSourceSaved": "Η μυστική πηγή αποθηκεύτηκε", "secretSourceDeleted": "Η μυστική πηγή διαγράφηκε", - "vaultManageProfiles": "Διαχείριση προφίλ Vault", - "vaultAddrLabel": "Διεύθυνση θησαυροφυλακίου", - "vaultNamespaceLabel": "Χώρος ονομάτων", - "vaultOidcMountLabel": "Μοντάζ ελέγχου ταυτότητας OIDC", - "vaultOidcRoleLabel": "Ρόλος OIDC", - "vaultSshMountLabel": "SSH Secrets Mount", - "vaultSshRoleLabel": "Ρόλος Υπογράφοντος SSH", - "vaultValidPrincipalsLabel": "Έγκυρες Αρχές", - "vaultKeyTypeLabel": "Εφήμερος Τύπος Κλειδιού", - "vaultSharedLabel": "Κοινοποίηση με όλους τους χρήστες", - "vaultCreateProfile": "Δημιουργία προφίλ", - "vaultProfileCreated": "Δημιουργήθηκε προφίλ Vault", - "vaultProfileSaved": "Το προφίλ του Vault αποθηκεύτηκε", - "vaultProfileDeleted": "Το προφίλ Vault διαγράφηκε", - "vaultProfileSaveFailed": "Αποτυχία αποθήκευσης προφίλ Vault", - "vaultProfileDeleteFailed": "Αποτυχία διαγραφής προφίλ Vault", - "vaultSaveProfile": "Αποθήκευση προφίλ", - "vaultProfileValidationError": "Απαιτούνται όνομα, διεύθυνση vault και ρόλος υπογράφοντος SSH", - "vaultNoProfiles": "Δεν υπάρχουν ακόμη προφίλ Vault.", - "vaultSharedBadge": "κοινόχρηστο", "keyTypeLabel": "Τύπος κλειδιού", "keyTypeAuto": "Αυτόματη ανίχνευση", "keyPasteTab": "Πάστα", diff --git a/src/ui/locales/translated/es_ES.json b/src/ui/locales/translated/es_ES.json index ab8f19ee8..aed162ff9 100644 --- a/src/ui/locales/translated/es_ES.json +++ b/src/ui/locales/translated/es_ES.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "Seleccione el método que Termix utilizará para autenticar esta conexión SSH. Los demás métodos no se intentarán automáticamente.", "storedCredential": "Credencial almacenada", "selectACredential": "Seleccionar una credencial...", - "vaultProfile": "Perfil de firmante de Vault", - "selectAVaultProfile": "Seleccionar un perfil de Vault...", - "vaultProfileHint": "La configuración proviene del perfil compartido; se iniciará sesión en Vault a través de OIDC al conectarse. No se almacenan secretos.", - "vaultNewProfile": "Nuevo perfil", "secretRefHint": "Puedes pegar una referencia de 1Password (op://vault/item/field) en lugar de la clave secreta; esta se obtiene de tu fuente de claves secretas al conectarte.", "secretSourcesManage": "Gestionar fuentes secretas", "secretSourcesTitle": "Fuentes secretas", @@ -739,26 +735,6 @@ "secretSourceRequired": "Se requieren nombre, URL del servidor y token.", "secretSourceSaved": "Fuente secreta guardada", "secretSourceDeleted": "Fuente secreta eliminada", - "vaultManageProfiles": "Administrar perfiles de Vault", - "vaultAddrLabel": "Dirección de Vault", - "vaultNamespaceLabel": "Espacio de nombres", - "vaultOidcMountLabel": "Montaje de autenticación OIDC", - "vaultOidcRoleLabel": "Rol OIDC", - "vaultSshMountLabel": "Montaje de secretos SSH", - "vaultSshRoleLabel": "Rol de firmante SSH", - "vaultValidPrincipalsLabel": "Principals válidos", - "vaultKeyTypeLabel": "Tipo de clave efímera", - "vaultSharedLabel": "Compartir con todos los usuarios", - "vaultCreateProfile": "Crear perfil", - "vaultProfileCreated": "Perfil de Vault creado", - "vaultProfileSaved": "Perfil de Vault guardado", - "vaultProfileDeleted": "Perfil de Vault eliminado", - "vaultProfileSaveFailed": "Error al guardar el perfil de Vault", - "vaultProfileDeleteFailed": "Error al eliminar el perfil de Vault", - "vaultSaveProfile": "Guardar perfil", - "vaultProfileValidationError": "Se requieren el nombre, la dirección de Vault y el rol de firmante SSH", - "vaultNoProfiles": "Aún no hay perfiles de Vault.", - "vaultSharedBadge": "compartido", "keyTypeLabel": "Tipo de clave", "keyTypeAuto": "Detección automática", "keyPasteTab": "Pegar", diff --git a/src/ui/locales/translated/fi_FI.json b/src/ui/locales/translated/fi_FI.json index a05bcb8cb..8615edf3a 100644 --- a/src/ui/locales/translated/fi_FI.json +++ b/src/ui/locales/translated/fi_FI.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "Valitse yksi menetelmä, jota Termix käyttää tämän SSH-yhteyden todentamiseen. Muita menetelmiä ei kokeilla automaattisesti.", "storedCredential": "Tallennetut tunnistetiedot", "selectACredential": "Valitse tunnistetiedot...", - "vaultProfile": "Varaston allekirjoittajan profiili", - "selectAVaultProfile": "Valitse holviprofiili...", - "vaultProfileHint": "Asetukset tulevat jaetusta profiilista; kirjaudut Holviin OIDC:n kautta, kun muodostat yhteyden. Salaisuuksia ei tallenneta.", - "vaultNewProfile": "Uusi profiili", "secretRefHint": "Voit liittää 1Password-viittauksen (op://vault/item/field) salaisuuden sijaan; se noudetaan salaisuuden lähteestäsi yhteyden muodostamisen yhteydessä.", "secretSourcesManage": "Hallitse salaisia lähteitä", "secretSourcesTitle": "Salaiset lähteet", @@ -739,26 +735,6 @@ "secretSourceRequired": "Nimi, palvelimen URL-osoite ja tunnus vaaditaan", "secretSourceSaved": "Salainen lähde tallennettu", "secretSourceDeleted": "Salainen lähde poistettu", - "vaultManageProfiles": "Hallinnoi holviprofiileja", - "vaultAddrLabel": "Holvin osoite", - "vaultNamespaceLabel": "Nimiavaruus", - "vaultOidcMountLabel": "OIDC-todennusliitos", - "vaultOidcRoleLabel": "OIDC-rooli", - "vaultSshMountLabel": "SSH-salaisuuksien kiinnitys", - "vaultSshRoleLabel": "SSH-allekirjoittajan rooli", - "vaultValidPrincipalsLabel": "Voimassa olevat pääasialliset", - "vaultKeyTypeLabel": "Lyhytaikainen avaintyyppi", - "vaultSharedLabel": "Jaa kaikkien käyttäjien kanssa", - "vaultCreateProfile": "Luo profiili", - "vaultProfileCreated": "Holvin profiili luotu", - "vaultProfileSaved": "Holvin profiili tallennettu", - "vaultProfileDeleted": "Holvin profiili poistettu", - "vaultProfileSaveFailed": "Holvin profiilin tallentaminen epäonnistui", - "vaultProfileDeleteFailed": "Holviprofiilin poistaminen epäonnistui", - "vaultSaveProfile": "Tallenna profiili", - "vaultProfileValidationError": "Nimi, holvin osoite ja SSH-allekirjoittajan rooli vaaditaan", - "vaultNoProfiles": "Ei vielä holviprofiileja.", - "vaultSharedBadge": "jaettu", "keyTypeLabel": "Avaimen tyyppi", "keyTypeAuto": "Automaattinen tunnistus", "keyPasteTab": "Liitä", diff --git a/src/ui/locales/translated/fr_FR.json b/src/ui/locales/translated/fr_FR.json index 5d489d97f..71b54b7cb 100644 --- a/src/ui/locales/translated/fr_FR.json +++ b/src/ui/locales/translated/fr_FR.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "Sélectionnez la méthode que Termix utilisera pour authentifier cette connexion SSH. Les autres méthodes ne seront pas essayées automatiquement.", "storedCredential": "Identifiant enregistré", "selectACredential": "Sélectionner un identifiant...", - "vaultProfile": "Profil de signature Vault", - "selectAVaultProfile": "Sélectionner un profil Vault...", - "vaultProfileHint": "Les paramètres proviennent du profil partagé ; vous vous connecterez à Vault via OIDC lors de la connexion. Aucun secret n'est stocké.", - "vaultNewProfile": "Nouveau profil", "secretRefHint": "Vous pouvez coller une référence 1Password (op://vault/item/field) à la place du secret ; elle sera récupérée depuis votre source de secret lors de la connexion.", "secretSourcesManage": "Gérer les sources secrètes", "secretSourcesTitle": "Sources secrètes", @@ -739,26 +735,6 @@ "secretSourceRequired": "Le nom, l'URL du serveur et le jeton sont requis.", "secretSourceSaved": "Source secrète sauvegardée", "secretSourceDeleted": "Source secrète supprimée", - "vaultManageProfiles": "Gérer les profils Vault", - "vaultAddrLabel": "Adresse Vault", - "vaultNamespaceLabel": "Espace de noms", - "vaultOidcMountLabel": "Point de montage d'authentification OIDC", - "vaultOidcRoleLabel": "Rôle OIDC", - "vaultSshMountLabel": "Point de montage des secrets SSH", - "vaultSshRoleLabel": "Rôle de signature SSH", - "vaultValidPrincipalsLabel": "Principaux valides", - "vaultKeyTypeLabel": "Type de clé éphémère", - "vaultSharedLabel": "Partager avec tous les utilisateurs", - "vaultCreateProfile": "Créer un profil", - "vaultProfileCreated": "Profil Vault créé", - "vaultProfileSaved": "Profil Vault enregistré", - "vaultProfileDeleted": "Profil Vault supprimé", - "vaultProfileSaveFailed": "Échec de l'enregistrement du profil Vault", - "vaultProfileDeleteFailed": "Échec de la suppression du profil Vault", - "vaultSaveProfile": "Enregistrer le profil", - "vaultProfileValidationError": "Le nom, l'adresse Vault et le rôle de signataire SSH sont requis", - "vaultNoProfiles": "Aucun profil Vault pour le moment", - "vaultSharedBadge": "partagé", "keyTypeLabel": "Type de clé", "keyTypeAuto": "Détection automatique", "keyPasteTab": "Coller", diff --git a/src/ui/locales/translated/he_IL.json b/src/ui/locales/translated/he_IL.json index 6d9a49bef..44396ea89 100644 --- a/src/ui/locales/translated/he_IL.json +++ b/src/ui/locales/translated/he_IL.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "בחר את השיטה שבה Termix ישתמש כדי לאמת חיבור SSH זה. שיטות אחרות לא ינוסו אוטומטית.", "storedCredential": "אישורים מאוחסנים", "selectACredential": "בחר אישור...", - "vaultProfile": "פרופיל חותם בכספת", - "selectAVaultProfile": "בחר פרופיל כספת...", - "vaultProfileHint": "ההגדרות מגיעות מהפרופיל המשותף; תיכנס לכספת דרך OIDC כשתתחבר. לא נשמרים סודות.", - "vaultNewProfile": "פרופיל חדש", "secretRefHint": "ניתן להדביק הפניה ל-1Password (op://vault/item/field) במקום הסוד; היא נלקחת ממקור הסוד שלך בעת ההתחברות.", "secretSourcesManage": "ניהול מקורות סודיים", "secretSourcesTitle": "מקורות סודיים", @@ -739,26 +735,6 @@ "secretSourceRequired": "נדרשים שם, כתובת URL של השרת וטופס", "secretSourceSaved": "מקור סודי נשמר", "secretSourceDeleted": "מקור סודי נמחק", - "vaultManageProfiles": "ניהול פרופילי כספת", - "vaultAddrLabel": "כתובת הכספת", - "vaultNamespaceLabel": "מרחב השמות", - "vaultOidcMountLabel": "אישור OIDC", - "vaultOidcRoleLabel": "תפקיד OIDC", - "vaultSshMountLabel": "סודות SSH מאוורר", - "vaultSshRoleLabel": "תפקיד חותם SSH", - "vaultValidPrincipalsLabel": "עקרונות תקפים", - "vaultKeyTypeLabel": "סוג מפתח זמני", - "vaultSharedLabel": "שתף עם כל המשתמשים", - "vaultCreateProfile": "צור פרופיל", - "vaultProfileCreated": "פרופיל הכספת נוצר", - "vaultProfileSaved": "פרופיל הכספת נשמר", - "vaultProfileDeleted": "פרופיל הכספת נמחק", - "vaultProfileSaveFailed": "שמירת פרופיל הכספת נכשלה", - "vaultProfileDeleteFailed": "נכשלה מחיקת פרופיל הכספת", - "vaultSaveProfile": "שמור פרופיל", - "vaultProfileValidationError": "שם, כתובת כספת ותפקיד חתימה ב-SSH נדרשים", - "vaultNoProfiles": "אין עדיין פרופילים בכספת.", - "vaultSharedBadge": "מְשׁוּתָף", "keyTypeLabel": "סוג מפתח", "keyTypeAuto": "זיהוי אוטומטי", "keyPasteTab": "לְהַדבִּיק", diff --git a/src/ui/locales/translated/hi_IN.json b/src/ui/locales/translated/hi_IN.json index 35c8f1673..be330b94d 100644 --- a/src/ui/locales/translated/hi_IN.json +++ b/src/ui/locales/translated/hi_IN.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "वह तरीका चुनें जिसका उपयोग टर्मिक्स इस एसएसएच कनेक्शन को प्रमाणित करने के लिए करेगा। अन्य तरीकों को स्वचालित रूप से नहीं आजमाया जाएगा।", "storedCredential": "संग्रहीत क्रेडेंशियल", "selectACredential": "एक क्रेडेंशियल चुनें...", - "vaultProfile": "Vault साइनर प्रोफ़ाइल", - "selectAVaultProfile": "एक Vault प्रोफ़ाइल चुनें...", - "vaultProfileHint": "सेटिंग साझा प्रोफ़ाइल से आती हैं; कनेक्ट करते समय आप OIDC के माध्यम से Vault में साइन इन करेंगे। कोई सीक्रेट संग्रहीत नहीं किए जाते।", - "vaultNewProfile": "नई प्रोफ़ाइल", "secretRefHint": "आप गुप्त पासवर्ड के स्थान पर 1Password संदर्भ (op://vault/item/field) पेस्ट कर सकते हैं; कनेक्ट करते समय इसे आपके गुप्त पासवर्ड स्रोत से प्राप्त किया जाता है।", "secretSourcesManage": "गुप्त स्रोतों का प्रबंधन करें", "secretSourcesTitle": "गुप्त सूत्रों", @@ -739,26 +735,6 @@ "secretSourceRequired": "नाम, सर्वर यूआरएल और टोकन आवश्यक हैं।", "secretSourceSaved": "गुप्त स्रोत सुरक्षित", "secretSourceDeleted": "गुप्त सूत्र हटा दिया गया", - "vaultManageProfiles": "Vault प्रोफ़ाइल प्रबंधित करें", - "vaultAddrLabel": "Vault पता", - "vaultNamespaceLabel": "नेमस्पेस", - "vaultOidcMountLabel": "OIDC ऑथ माउंट", - "vaultOidcRoleLabel": "OIDC रोल", - "vaultSshMountLabel": "SSH सीक्रेट माउंट", - "vaultSshRoleLabel": "SSH साइनर रोल", - "vaultValidPrincipalsLabel": "मान्य प्रिंसिपल", - "vaultKeyTypeLabel": "एफेमरल कुंजी प्रकार", - "vaultSharedLabel": "सभी उपयोगकर्ताओं के साथ साझा करें", - "vaultCreateProfile": "प्रोफ़ाइल बनाएँ", - "vaultProfileCreated": "Vault प्रोफ़ाइल बनाई गई", - "vaultProfileSaved": "Vault प्रोफ़ाइल सहेजी गई", - "vaultProfileDeleted": "Vault प्रोफ़ाइल हटाई गई", - "vaultProfileSaveFailed": "Vault प्रोफ़ाइल सहेजने में विफल", - "vaultProfileDeleteFailed": "Vault प्रोफ़ाइल हटाने में विफल", - "vaultSaveProfile": "प्रोफ़ाइल सहेजें", - "vaultProfileValidationError": "नाम, Vault पता और SSH हस्ताक्षरकर्ता भूमिका आवश्यक हैं", - "vaultNoProfiles": "अभी तक कोई Vault प्रोफ़ाइल नहीं है।", - "vaultSharedBadge": "साझा", "keyTypeLabel": "कुंजी प्रकार", "keyTypeAuto": "स्वतः पहचानें", "keyPasteTab": "चिपकाएँ", diff --git a/src/ui/locales/translated/hu_HU.json b/src/ui/locales/translated/hu_HU.json index d84d07051..44dec1a04 100644 --- a/src/ui/locales/translated/hu_HU.json +++ b/src/ui/locales/translated/hu_HU.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "Válassza ki azt az egy módszert, amelyet a Termix az SSH-kapcsolat hitelesítéséhez használ. A többi módszert a rendszer nem próbálja ki automatikusan.", "storedCredential": "Tárolt hitelesítő adatok", "selectACredential": "Válasszon hitelesítő adatot...", - "vaultProfile": "Vault aláírói profil", - "selectAVaultProfile": "Válasszon ki egy Vault profilt...", - "vaultProfileHint": "A beállítások a megosztott profilból származnak; a csatlakozáskor OIDC-n keresztül fog bejelentkezni a Vaultba. A rendszer nem tárol titkos adatokat.", - "vaultNewProfile": "Új profil", "secretRefHint": "A titkos kód helyett beilleszthet egy 1Password hivatkozást (op://vault/item/field); ezt a titkos kód forrásából kéri le a rendszer a csatlakozáskor.", "secretSourcesManage": "Titkos források kezelése", "secretSourcesTitle": "Titkos források", @@ -739,26 +735,6 @@ "secretSourceRequired": "Név, szerver URL és token megadása kötelező", "secretSourceSaved": "Titkos forrás mentve", "secretSourceDeleted": "Titkos forrás törölve", - "vaultManageProfiles": "Vault profilok kezelése", - "vaultAddrLabel": "Trezor címe", - "vaultNamespaceLabel": "Névtér", - "vaultOidcMountLabel": "OIDC hitelesítési csatolás", - "vaultOidcRoleLabel": "OIDC szerepkör", - "vaultSshMountLabel": "SSH titkok felhúzása", - "vaultSshRoleLabel": "SSH aláíró szerepkör", - "vaultValidPrincipalsLabel": "Érvényes megbízók", - "vaultKeyTypeLabel": "Efemer kulcstípus", - "vaultSharedLabel": "Megosztás az összes felhasználóval", - "vaultCreateProfile": "Profil létrehozása", - "vaultProfileCreated": "Létrehozva a trezor profilja", - "vaultProfileSaved": "A trezor profilja mentve", - "vaultProfileDeleted": "Tárolóprofil törölve", - "vaultProfileSaveFailed": "Nem sikerült menteni a Vault profilját", - "vaultProfileDeleteFailed": "Nem sikerült törölni a Vault profilját", - "vaultSaveProfile": "Profil mentése", - "vaultProfileValidationError": "Név, trezor címe és SSH aláírói szerepkör megadása kötelező.", - "vaultNoProfiles": "Még nincsenek Vault-profilok.", - "vaultSharedBadge": "megosztott", "keyTypeLabel": "Kulcstípus", "keyTypeAuto": "Automatikus felismerés", "keyPasteTab": "Paszta", diff --git a/src/ui/locales/translated/id_ID.json b/src/ui/locales/translated/id_ID.json index 0206606eb..459a3152f 100644 --- a/src/ui/locales/translated/id_ID.json +++ b/src/ui/locales/translated/id_ID.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "Pilih salah satu metode yang akan digunakan Termix untuk mengautentikasi koneksi SSH ini. Metode lain tidak dicoba secara otomatis.", "storedCredential": "Kredensial Tersimpan", "selectACredential": "Pilih kredensial...", - "vaultProfile": "Profil Penandatangan Vault", - "selectAVaultProfile": "Pilih profil Vault...", - "vaultProfileHint": "Pengaturan berasal dari profil bersama; Anda akan masuk ke Vault melalui OIDC saat terhubung. Tidak ada rahasia yang disimpan.", - "vaultNewProfile": "Profil baru", "secretRefHint": "Anda dapat menempelkan referensi 1Password (op://vault/item/field) sebagai pengganti rahasia; referensi tersebut akan diambil dari sumber rahasia Anda saat terhubung.", "secretSourcesManage": "Mengelola sumber rahasia", "secretSourcesTitle": "Sumber rahasia", @@ -739,26 +735,6 @@ "secretSourceRequired": "Nama, URL server, dan token wajib diisi.", "secretSourceSaved": "Sumber rahasia tersimpan", "secretSourceDeleted": "Sumber rahasia telah dihapus.", - "vaultManageProfiles": "Kelola profil Vault", - "vaultAddrLabel": "Alamat Brankas", - "vaultNamespaceLabel": "Ruang nama", - "vaultOidcMountLabel": "OIDC Otorisasi Pemasangan", - "vaultOidcRoleLabel": "Peran OIDC", - "vaultSshMountLabel": "Pemasangan Rahasia SSH", - "vaultSshRoleLabel": "Peran Penandatangan SSH", - "vaultValidPrincipalsLabel": "Prinsip-prinsip yang Sah", - "vaultKeyTypeLabel": "Jenis Kunci Sementara", - "vaultSharedLabel": "Bagikan dengan semua pengguna", - "vaultCreateProfile": "Buat profil", - "vaultProfileCreated": "Profil Vault telah dibuat.", - "vaultProfileSaved": "Profil Vault tersimpan.", - "vaultProfileDeleted": "Profil Vault dihapus", - "vaultProfileSaveFailed": "Gagal menyimpan profil Vault", - "vaultProfileDeleteFailed": "Gagal menghapus profil Vault", - "vaultSaveProfile": "Simpan profil", - "vaultProfileValidationError": "Nama, alamat Vault, dan peran penandatangan SSH wajib diisi.", - "vaultNoProfiles": "Belum ada profil Vault.", - "vaultSharedBadge": "dibagikan", "keyTypeLabel": "Jenis Kunci", "keyTypeAuto": "Deteksi Otomatis", "keyPasteTab": "Pasta", diff --git a/src/ui/locales/translated/it_IT.json b/src/ui/locales/translated/it_IT.json index 462d65593..9642a6b5f 100644 --- a/src/ui/locales/translated/it_IT.json +++ b/src/ui/locales/translated/it_IT.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "Seleziona il metodo che Termix utilizzerà per autenticare questa connessione SSH. Gli altri metodi non verranno tentati automaticamente.", "storedCredential": "Credenziale memorizzata", "selectACredential": "Seleziona una credenziale...", - "vaultProfile": "Profilo firmatario Vault", - "selectAVaultProfile": "Seleziona un profilo Vault...", - "vaultProfileHint": "Le impostazioni provengono dal profilo condiviso; effettuerai l'accesso a Vault tramite OIDC quando ti connetti. Nessun segreto è memorizzato.", - "vaultNewProfile": "Nuovo profilo", "secretRefHint": "Al posto del segreto, puoi incollare un riferimento a 1Password (op://vault/item/field); questo verrà recuperato dalla tua fonte di segreti al momento della connessione.", "secretSourcesManage": "Gestire le fonti segrete", "secretSourcesTitle": "Fonti segrete", @@ -739,26 +735,6 @@ "secretSourceRequired": "Sono richiesti nome, URL del server e token.", "secretSourceSaved": "fonte segreta salvata", "secretSourceDeleted": "Fonte segreta eliminata", - "vaultManageProfiles": "Gestisci profili Vault", - "vaultAddrLabel": "Indirizzo Vault", - "vaultNamespaceLabel": "Namespace", - "vaultOidcMountLabel": "Mount di autenticazione OIDC", - "vaultOidcRoleLabel": "Ruolo OIDC", - "vaultSshMountLabel": "Mount dei segreti SSH", - "vaultSshRoleLabel": "Ruolo firmatario SSH", - "vaultValidPrincipalsLabel": "Principal validi", - "vaultKeyTypeLabel": "Tipo di chiave effimera", - "vaultSharedLabel": "Condividi con tutti gli utenti", - "vaultCreateProfile": "Crea profilo", - "vaultProfileCreated": "Profilo Vault creato", - "vaultProfileSaved": "Profilo Vault salvato", - "vaultProfileDeleted": "Profilo Vault eliminato", - "vaultProfileSaveFailed": "Salvataggio profilo Vault non riuscito", - "vaultProfileDeleteFailed": "Eliminazione profilo Vault non riuscita", - "vaultSaveProfile": "Salva profilo", - "vaultProfileValidationError": "Nome, indirizzo Vault e ruolo di firma SSH sono obbligatori", - "vaultNoProfiles": "Nessun profilo Vault ancora.", - "vaultSharedBadge": "condiviso", "keyTypeLabel": "Tipo chiave", "keyTypeAuto": "Rilevamento automatico", "keyPasteTab": "Incolla", diff --git a/src/ui/locales/translated/ja_JP.json b/src/ui/locales/translated/ja_JP.json index d3d43eb7a..b86f32ad0 100644 --- a/src/ui/locales/translated/ja_JP.json +++ b/src/ui/locales/translated/ja_JP.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "TermixがこのSSH接続を認証するために使用する方法を1つ選択してください。他の方法は自動的には試行されません。", "storedCredential": "保存済み認証情報", "selectACredential": "認証情報を選択...", - "vaultProfile": "Vault 署名者プロファイル", - "selectAVaultProfile": "Vault プロファイルを選択...", - "vaultProfileHint": "設定は共有プロファイルから取得され、接続時に OIDC で Vault にサインインします。シークレットは保存されません。", - "vaultNewProfile": "新規プロファイル", "secretRefHint": "シークレットの代わりに1Passwordの参照(op://vault/item/field)を貼り付けることもできます。これは接続時にシークレットソースから取得されます。", "secretSourcesManage": "秘密情報源の管理", "secretSourcesTitle": "秘密の情報源", @@ -739,26 +735,6 @@ "secretSourceRequired": "名前、サーバーURL、トークンは必須です。", "secretSourceSaved": "秘密の情報源が保存されました", "secretSourceDeleted": "秘密情報源を削除", - "vaultManageProfiles": "Vault プロファイルの管理", - "vaultAddrLabel": "Vault アドレス", - "vaultNamespaceLabel": "名前空間", - "vaultOidcMountLabel": "OIDC 認証マウント", - "vaultOidcRoleLabel": "OIDC ロール", - "vaultSshMountLabel": "SSH シークレットマウント", - "vaultSshRoleLabel": "SSH 署名者ロール", - "vaultValidPrincipalsLabel": "有効なプリンシパル", - "vaultKeyTypeLabel": "エフェメラルキータイプ", - "vaultSharedLabel": "すべてのユーザーと共有", - "vaultCreateProfile": "プロファイルを作成", - "vaultProfileCreated": "Vaultプロファイルが作成されました", - "vaultProfileSaved": "Vaultプロファイルが保存されました", - "vaultProfileDeleted": "Vaultプロファイルが削除されました", - "vaultProfileSaveFailed": "Vaultプロファイルの保存に失敗しました", - "vaultProfileDeleteFailed": "Vaultプロファイルの削除に失敗しました", - "vaultSaveProfile": "プロファイルを保存", - "vaultProfileValidationError": "名前、Vaultアドレス、SSH署名ロールは必須です", - "vaultNoProfiles": "まだVaultプロファイルがありません。", - "vaultSharedBadge": "共有", "keyTypeLabel": "鍵の種類", "keyTypeAuto": "自動検出", "keyPasteTab": "貼り付け", diff --git a/src/ui/locales/translated/ko_KR.json b/src/ui/locales/translated/ko_KR.json index 3b2e4db01..0184526d7 100644 --- a/src/ui/locales/translated/ko_KR.json +++ b/src/ui/locales/translated/ko_KR.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "Termix에서 이 SSH 연결을 인증하는 데 사용할 방법을 하나 선택하십시오. 다른 방법은 자동으로 시도되지 않습니다.", "storedCredential": "저장된 자격 증명", "selectACredential": "자격 증명 선택...", - "vaultProfile": "Vault 서명자 프로필", - "selectAVaultProfile": "Vault 프로필 선택...", - "vaultProfileHint": "설정은 공유 프로필에서 가져옵니다. 연결 시 OIDC를 통해 Vault에 로그인하게 됩니다. 비밀은 저장되지 않습니다.", - "vaultNewProfile": "새 프로필", "secretRefHint": "비밀 키 대신 1Password 참조(op://vault/item/field)를 붙여넣을 수 있습니다. 연결 시 비밀 키 소스에서 참조가 자동으로 가져와집니다.", "secretSourcesManage": "비밀 소스를 관리하세요", "secretSourcesTitle": "비밀 정보원", @@ -739,26 +735,6 @@ "secretSourceRequired": "이름, 서버 URL 및 토큰은 필수 입력 사항입니다.", "secretSourceSaved": "비밀 소스 저장됨", "secretSourceDeleted": "비밀 소스 삭제됨", - "vaultManageProfiles": "Vault 프로필 관리", - "vaultAddrLabel": "Vault 주소", - "vaultNamespaceLabel": "네임스페이스", - "vaultOidcMountLabel": "OIDC 인증 마운트", - "vaultOidcRoleLabel": "OIDC 역할", - "vaultSshMountLabel": "SSH 시크릿 마운트", - "vaultSshRoleLabel": "SSH 서명자 역할", - "vaultValidPrincipalsLabel": "유효한 프린시펄", - "vaultKeyTypeLabel": "임시 키 유형", - "vaultSharedLabel": "모든 사용자와 공유", - "vaultCreateProfile": "프로필 생성", - "vaultProfileCreated": "Vault 프로필 생성됨", - "vaultProfileSaved": "Vault 프로필 저장됨", - "vaultProfileDeleted": "Vault 프로필 삭제됨", - "vaultProfileSaveFailed": "Vault 프로필 저장 실패", - "vaultProfileDeleteFailed": "Vault 프로필 삭제 실패", - "vaultSaveProfile": "프로필 저장", - "vaultProfileValidationError": "이름, Vault 주소 및 SSH 서명자 역할이 필요합니다.", - "vaultNoProfiles": "아직 Vault 프로필이 없습니다.", - "vaultSharedBadge": "공유", "keyTypeLabel": "키 유형", "keyTypeAuto": "자동 감지", "keyPasteTab": "붙여넣기", diff --git a/src/ui/locales/translated/nl_NL.json b/src/ui/locales/translated/nl_NL.json index 2997a793d..5ddfd405d 100644 --- a/src/ui/locales/translated/nl_NL.json +++ b/src/ui/locales/translated/nl_NL.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "Selecteer de methode die Termix zal gebruiken om deze SSH-verbinding te authenticeren. Andere methoden worden niet automatisch geprobeerd.", "storedCredential": "Opgeslagen inloggegevens", "selectACredential": "Selecteer een certificaat...", - "vaultProfile": "Vault-ondertekenaarprofiel", - "selectAVaultProfile": "Selecteer een Vault-profiel...", - "vaultProfileHint": "De instellingen zijn afkomstig van het gedeelde profiel; u meldt zich bij Vault aan via OIDC wanneer u verbinding maakt. Er worden geen geheimen opgeslagen.", - "vaultNewProfile": "Nieuw profiel", "secretRefHint": "Je kunt in plaats van het geheim een 1Password-referentie (op://vault/item/field) plakken; deze wordt bij het verbinden opgehaald uit je geheime bron.", "secretSourcesManage": "Beheer geheime bronnen", "secretSourcesTitle": "Geheime bronnen", @@ -739,26 +735,6 @@ "secretSourceRequired": "Naam, server-URL en token zijn vereist.", "secretSourceSaved": "Geheime bron opgeslagen", "secretSourceDeleted": "Geheime bron verwijderd", - "vaultManageProfiles": "Beheer Vault-profielen", - "vaultAddrLabel": "Kluisadres", - "vaultNamespaceLabel": "Naamruimte", - "vaultOidcMountLabel": "OIDC-authenticatiekoppeling", - "vaultOidcRoleLabel": "OIDC-rol", - "vaultSshMountLabel": "SSH-geheimen Mount", - "vaultSshRoleLabel": "SSH-ondertekenaarrol", - "vaultValidPrincipalsLabel": "Geldige principes", - "vaultKeyTypeLabel": "Tijdelijk sleuteltype", - "vaultSharedLabel": "Delen met alle gebruikers", - "vaultCreateProfile": "Profiel aanmaken", - "vaultProfileCreated": "Vault-profiel aangemaakt", - "vaultProfileSaved": "Vault-profiel opgeslagen", - "vaultProfileDeleted": "Kluisprofiel verwijderd", - "vaultProfileSaveFailed": "Het opslaan van het Vault-profiel is mislukt.", - "vaultProfileDeleteFailed": "Het verwijderen van het Vault-profiel is mislukt.", - "vaultSaveProfile": "Profiel opslaan", - "vaultProfileValidationError": "Naam, kluisadres en SSH-ondertekeningsrol zijn vereist.", - "vaultNoProfiles": "Nog geen Vault-profielen beschikbaar.", - "vaultSharedBadge": "gedeeld", "keyTypeLabel": "Sleuteltype", "keyTypeAuto": "Automatische detectie", "keyPasteTab": "Plakken", diff --git a/src/ui/locales/translated/no_NO.json b/src/ui/locales/translated/no_NO.json index c7b909a67..e3db8f564 100644 --- a/src/ui/locales/translated/no_NO.json +++ b/src/ui/locales/translated/no_NO.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "Velg den metoden Termix vil bruke for å autentisere denne SSH-tilkoblingen. Andre metoder blir ikke prøvd automatisk.", "storedCredential": "Lagret legitimasjon", "selectACredential": "Velg en legitimasjon...", - "vaultProfile": "Profil for hvelvsigner", - "selectAVaultProfile": "Velg en Vault-profil...", - "vaultProfileHint": "Innstillingene kommer fra den delte profilen. Du logger deg på Vault via OIDC når du kobler til. Ingen hemmeligheter lagres.", - "vaultNewProfile": "Ny profil", "secretRefHint": "Du kan lime inn en 1Password-referanse (op://vault/item/field) i stedet for hemmeligheten; den hentes fra hemmelighetskilden din når du kobler til.", "secretSourcesManage": "Administrer hemmelige kilder", "secretSourcesTitle": "Hemmelige kilder", @@ -739,26 +735,6 @@ "secretSourceRequired": "Navn, server-URL og token er obligatorisk", "secretSourceSaved": "Hemmelig kilde lagret", "secretSourceDeleted": "Hemmelig kilde slettet", - "vaultManageProfiles": "Administrer Vault-profiler", - "vaultAddrLabel": "Hvelvadresse", - "vaultNamespaceLabel": "Navnerom", - "vaultOidcMountLabel": "OIDC-autentiseringsmontering", - "vaultOidcRoleLabel": "OIDC-rolle", - "vaultSshMountLabel": "SSH-hemmeligheter monteres", - "vaultSshRoleLabel": "SSH-signatørrolle", - "vaultValidPrincipalsLabel": "Gyldige prinsipaler", - "vaultKeyTypeLabel": "Kortvarig nøkkeltype", - "vaultSharedLabel": "Del med alle brukere", - "vaultCreateProfile": "Opprett profil", - "vaultProfileCreated": "Vault-profil opprettet", - "vaultProfileSaved": "Vault-profilen er lagret", - "vaultProfileDeleted": "Vault-profilen er slettet", - "vaultProfileSaveFailed": "Kunne ikke lagre Vault-profilen", - "vaultProfileDeleteFailed": "Kunne ikke slette Vault-profilen", - "vaultSaveProfile": "Lagre profil", - "vaultProfileValidationError": "Navn, hvelvadresse og SSH-signerrolle er obligatorisk", - "vaultNoProfiles": "Ingen Vault-profiler ennå.", - "vaultSharedBadge": "delt", "keyTypeLabel": "Nøkkeltype", "keyTypeAuto": "Automatisk deteksjon", "keyPasteTab": "Lim inn", diff --git a/src/ui/locales/translated/pl_PL.json b/src/ui/locales/translated/pl_PL.json index 8b1542670..d795079b6 100644 --- a/src/ui/locales/translated/pl_PL.json +++ b/src/ui/locales/translated/pl_PL.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "Wybierz jedną metodę, której Termix użyje do uwierzytelnienia tego połączenia SSH. Inne metody nie są próbowane automatycznie.", "storedCredential": "Zapisane poświadczenie", "selectACredential": "Wybierz poświadczenie...", - "vaultProfile": "Profil podpisujący Vault", - "selectAVaultProfile": "Wybierz profil Vault...", - "vaultProfileHint": "Ustawienia pochodzą z profilu współdzielonego. Po połączeniu zalogujesz się do Vault przez OIDC. Nie są przechowywane żadne sekrety.", - "vaultNewProfile": "Nowy profil", "secretRefHint": "Zamiast klucza tajnego możesz wkleić odnośnik 1Password (op://vault/item/field). Zostanie on pobrany ze źródła klucza tajnego podczas nawiązywania połączenia.", "secretSourcesManage": "Zarządzaj tajnymi źródłami", "secretSourcesTitle": "Tajne źródła", @@ -739,26 +735,6 @@ "secretSourceRequired": "Wymagane są nazwa, adres URL serwera i token", "secretSourceSaved": "Tajne źródło uratowane", "secretSourceDeleted": "Usunięto tajne źródło", - "vaultManageProfiles": "Zarządzaj profilami Vault", - "vaultAddrLabel": "Adres Vault", - "vaultNamespaceLabel": "Przestrzeń nazw", - "vaultOidcMountLabel": "Mount uwierzytelniania OIDC", - "vaultOidcRoleLabel": "Rola OIDC", - "vaultSshMountLabel": "Mount sekretów SSH", - "vaultSshRoleLabel": "Rola podpisująca SSH", - "vaultValidPrincipalsLabel": "Dozwoleni użytkownicy (principals)", - "vaultKeyTypeLabel": "Typ klucza efemerycznego", - "vaultSharedLabel": "Udostępnij wszystkim użytkownikom", - "vaultCreateProfile": "Utwórz profil", - "vaultProfileCreated": "Utworzono profil Vault", - "vaultProfileSaved": "Zapisano profil Vault", - "vaultProfileDeleted": "Usunięto profil Vault", - "vaultProfileSaveFailed": "Nie udało się zapisać profilu Vault", - "vaultProfileDeleteFailed": "Nie udało się usunąć profilu Vault", - "vaultSaveProfile": "Zapisz profil", - "vaultProfileValidationError": "Wymagane są nazwa, adres Vault i rola podpisująca SSH", - "vaultNoProfiles": "Brak profili Vault.", - "vaultSharedBadge": "współdzielony", "keyTypeLabel": "Typ klucza", "keyTypeAuto": "Automatyczne wykrywanie", "keyPasteTab": "Wklej", diff --git a/src/ui/locales/translated/pt_BR.json b/src/ui/locales/translated/pt_BR.json index 2691c87e1..5394cd720 100644 --- a/src/ui/locales/translated/pt_BR.json +++ b/src/ui/locales/translated/pt_BR.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "Selecione o método que o Termix usará para autenticar esta conexão SSH. Outros métodos não serão tentados automaticamente.", "storedCredential": "Credencial armazenada", "selectACredential": "Selecione uma credencial...", - "vaultProfile": "Perfil do assinante do Vault", - "selectAVaultProfile": "Selecione um perfil do Vault...", - "vaultProfileHint": "As configurações são provenientes do perfil compartilhado; você fará login no Vault via OIDC ao se conectar. Nenhum segredo é armazenado.", - "vaultNewProfile": "Novo perfil", "secretRefHint": "Você pode colar uma referência do 1Password (op://vault/item/field) em vez do segredo; ela é obtida da sua fonte de segredos ao conectar-se.", "secretSourcesManage": "Gerenciar fontes secretas", "secretSourcesTitle": "Fontes secretas", @@ -739,26 +735,6 @@ "secretSourceRequired": "Nome, URL do servidor e token são obrigatórios.", "secretSourceSaved": "Fonte secreta salva", "secretSourceDeleted": "Fonte secreta excluída", - "vaultManageProfiles": "Gerenciar perfis do Vault", - "vaultAddrLabel": "Endereço do cofre", - "vaultNamespaceLabel": "Espaço de nomes", - "vaultOidcMountLabel": "Montagem de autenticação OIDC", - "vaultOidcRoleLabel": "Função OIDC", - "vaultSshMountLabel": "Montagem de segredos SSH", - "vaultSshRoleLabel": "Função de assinatura SSH", - "vaultValidPrincipalsLabel": "Principais válidos", - "vaultKeyTypeLabel": "Tipo de chave efêmera", - "vaultSharedLabel": "Compartilhe com todos os usuários", - "vaultCreateProfile": "Criar perfil", - "vaultProfileCreated": "Perfil do Vault criado", - "vaultProfileSaved": "Perfil do Vault salvo", - "vaultProfileDeleted": "Perfil do Vault excluído", - "vaultProfileSaveFailed": "Falha ao salvar o perfil do Vault", - "vaultProfileDeleteFailed": "Falha ao excluir o perfil do Vault", - "vaultSaveProfile": "Salvar perfil", - "vaultProfileValidationError": "Nome, endereço do Vault e função de assinatura SSH são obrigatórios.", - "vaultNoProfiles": "Ainda não há perfis no Vault.", - "vaultSharedBadge": "compartilhado", "keyTypeLabel": "Tipo de chave", "keyTypeAuto": "Detecção automática", "keyPasteTab": "Colar", diff --git a/src/ui/locales/translated/pt_PT.json b/src/ui/locales/translated/pt_PT.json index 04d61addd..c52026dc4 100644 --- a/src/ui/locales/translated/pt_PT.json +++ b/src/ui/locales/translated/pt_PT.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "Selecione o método que o Termix irá utilizar para autenticar esta ligação SSH. Outros métodos não serão tentados automaticamente.", "storedCredential": "Credencial guardada", "selectACredential": "Selecionar uma credencial...", - "vaultProfile": "Perfil de assinatura Vault", - "selectAVaultProfile": "Selecionar um perfil Vault...", - "vaultProfileHint": "As definições provêm do perfil partilhado; ao ligar, iniciará sessão no Vault via OIDC. Nenhum segredo é armazenado.", - "vaultNewProfile": "Novo perfil", "secretRefHint": "Pode colar uma referência do 1Password (op://vault/item/field) em vez do segredo; é obtida a partir da sua fonte de segredos ao conectar-se.", "secretSourcesManage": "Gerir fontes secretas", "secretSourcesTitle": "Fontes secretas", @@ -739,26 +735,6 @@ "secretSourceRequired": "Nome, URL do servidor e token são obrigatórios.", "secretSourceSaved": "Fonte secreta guardada", "secretSourceDeleted": "Fonte secreta excluída", - "vaultManageProfiles": "Gerir perfis Vault", - "vaultAddrLabel": "Endereço Vault", - "vaultNamespaceLabel": "Namespace", - "vaultOidcMountLabel": "Mount de autenticação OIDC", - "vaultOidcRoleLabel": "Função OIDC", - "vaultSshMountLabel": "Mount de segredos SSH", - "vaultSshRoleLabel": "Função de assinatura SSH", - "vaultValidPrincipalsLabel": "Principais válidos", - "vaultKeyTypeLabel": "Tipo de chave efémera", - "vaultSharedLabel": "Partilhar com todos os utilizadores", - "vaultCreateProfile": "Criar perfil", - "vaultProfileCreated": "Perfil Vault criado", - "vaultProfileSaved": "Perfil Vault guardado", - "vaultProfileDeleted": "Perfil Vault eliminado", - "vaultProfileSaveFailed": "Falha ao guardar perfil Vault", - "vaultProfileDeleteFailed": "Falha ao eliminar perfil Vault", - "vaultSaveProfile": "Guardar perfil", - "vaultProfileValidationError": "Nome, endereço Vault e função de signatário SSH são obrigatórios", - "vaultNoProfiles": "Ainda sem perfis Vault.", - "vaultSharedBadge": "partilhado", "keyTypeLabel": "Tipo de chave", "keyTypeAuto": "Detetar automaticamente", "keyPasteTab": "Colar", diff --git a/src/ui/locales/translated/ro_RO.json b/src/ui/locales/translated/ro_RO.json index 84d575c8a..0a0373ec9 100644 --- a/src/ui/locales/translated/ro_RO.json +++ b/src/ui/locales/translated/ro_RO.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "Selectați metoda pe care Termix o va folosi pentru autentificarea acestei conexiuni SSH. Celelalte metode nu sunt încercate automat.", "storedCredential": "Acreditare stocată", "selectACredential": "Selectați o acreditare...", - "vaultProfile": "Profilul semnatarului din seif", - "selectAVaultProfile": "Selectați un profil Vault...", - "vaultProfileHint": "Setările provin din profilul partajat; te vei conecta la Vault prin OIDC când te conectezi. Nu sunt stocate secrete.", - "vaultNewProfile": "Profil nou", "secretRefHint": "Puteți lipi o referință 1Password (op://vault/item/field) în loc de secret; acesta este preluat din sursa secretului la conectare.", "secretSourcesManage": "Gestionați sursele secrete", "secretSourcesTitle": "Surse secrete", @@ -739,26 +735,6 @@ "secretSourceRequired": "Numele, adresa URL a serverului și tokenul sunt obligatorii", "secretSourceSaved": "Sursă secretă salvată", "secretSourceDeleted": "Sursa secretă a fost ștearsă", - "vaultManageProfiles": "Gestionați profilurile din seif", - "vaultAddrLabel": "Adresă seif", - "vaultNamespaceLabel": "Spațiu de nume", - "vaultOidcMountLabel": "Montare autentificare OIDC", - "vaultOidcRoleLabel": "Rolul OIDC", - "vaultSshMountLabel": "Secretele SSH Mount", - "vaultSshRoleLabel": "Rolul semnatarului SSH", - "vaultValidPrincipalsLabel": "Principali validi", - "vaultKeyTypeLabel": "Tip de cheie efemeră", - "vaultSharedLabel": "Partajați cu toți utilizatorii", - "vaultCreateProfile": "Creați un profil", - "vaultProfileCreated": "Profilul seifului a fost creat", - "vaultProfileSaved": "Profilul seifului a fost salvat", - "vaultProfileDeleted": "Profilul seifului a fost șters", - "vaultProfileSaveFailed": "Nu s-a putut salva profilul din Seif", - "vaultProfileDeleteFailed": "Nu s-a putut șterge profilul Vault", - "vaultSaveProfile": "Salvați profilul", - "vaultProfileValidationError": "Numele, adresa seifului și rolul semnatarului SSH sunt obligatorii.", - "vaultNoProfiles": "Încă nu există profiluri Vault.", - "vaultSharedBadge": "partajat", "keyTypeLabel": "Tipul cheii", "keyTypeAuto": "Detectare automată", "keyPasteTab": "Pastă", diff --git a/src/ui/locales/translated/ru_RU.json b/src/ui/locales/translated/ru_RU.json index 0bec6dc37..523aae370 100644 --- a/src/ui/locales/translated/ru_RU.json +++ b/src/ui/locales/translated/ru_RU.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "Выберите один из методов, который Termix будет использовать для аутентификации этого SSH-соединения. Другие методы автоматически не используются.", "storedCredential": "Сохранённые учётные данные", "selectACredential": "Выберите учётные данные...", - "vaultProfile": "Профиль подписчика Vault", - "selectAVaultProfile": "Выберите профиль Vault...", - "vaultProfileHint": "Настройки берутся из общего профиля; при подключении вы войдёте в Vault через OIDC. Секреты не хранятся.", - "vaultNewProfile": "Новый профиль", "secretRefHint": "Вместо секретного ключа можно вставить ссылку на 1Password (op://vault/item/field); она будет получена из вашего источника секретных ключей при подключении.", "secretSourcesManage": "Управление секретными источниками", "secretSourcesTitle": "Секретные источники", @@ -739,26 +735,6 @@ "secretSourceRequired": "Необходимо указать имя, URL-адрес сервера и токен.", "secretSourceSaved": "Секретный источник сохранен", "secretSourceDeleted": "Секретный источник удален", - "vaultManageProfiles": "Управление профилями Vault", - "vaultAddrLabel": "Адрес Vault", - "vaultNamespaceLabel": "Пространство имён", - "vaultOidcMountLabel": "Монтирование аутентификации OIDC", - "vaultOidcRoleLabel": "Роль OIDC", - "vaultSshMountLabel": "Монтирование секретов SSH", - "vaultSshRoleLabel": "Роль подписчика SSH", - "vaultValidPrincipalsLabel": "Допустимые принципалы", - "vaultKeyTypeLabel": "Тип эфемерного ключа", - "vaultSharedLabel": "Общий доступ для всех пользователей", - "vaultCreateProfile": "Создать профиль", - "vaultProfileCreated": "Профиль Vault создан", - "vaultProfileSaved": "Профиль Vault сохранён", - "vaultProfileDeleted": "Профиль Vault удалён", - "vaultProfileSaveFailed": "Не удалось сохранить профиль Vault", - "vaultProfileDeleteFailed": "Не удалось удалить профиль Vault", - "vaultSaveProfile": "Сохранить профиль", - "vaultProfileValidationError": "Требуются имя, адрес Vault и роль SSH подписанта", - "vaultNoProfiles": "Пока нет профилей Vault.", - "vaultSharedBadge": "общий", "keyTypeLabel": "Тип ключа", "keyTypeAuto": "Автоопределение", "keyPasteTab": "Вставить", diff --git a/src/ui/locales/translated/sr_SP.json b/src/ui/locales/translated/sr_SP.json index 0b52a4f6c..a54b3b25d 100644 --- a/src/ui/locales/translated/sr_SP.json +++ b/src/ui/locales/translated/sr_SP.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "Изаберите једну методу коју ће Termix користити за аутентификацију ове SSH везе. Остале методе се не испробавају аутоматски.", "storedCredential": "Сачувани акредитив", "selectACredential": "Изаберите акредитив...", - "vaultProfile": "Профил потписника трезора", - "selectAVaultProfile": "Изаберите профил трезора...", - "vaultProfileHint": "Подешавања долазе из дељеног профила; пријавићете се у Vault преко OIDC-а када се повежете. Ниједне тајне се не чувају.", - "vaultNewProfile": "Нови профил", "secretRefHint": "Можете да налепите референцу 1Password (op://vault/item/field) уместо тајне лозинке; она се преузима из вашег тајног извора приликом повезивања.", "secretSourcesManage": "Управљајте тајним изворима", "secretSourcesTitle": "Тајни извори", @@ -739,26 +735,6 @@ "secretSourceRequired": "Потребно је име, URL сервера и токен", "secretSourceSaved": "Тајни извор сачуван", "secretSourceDeleted": "Тајни извор је обрисан", - "vaultManageProfiles": "Управљање профилима трезора", - "vaultAddrLabel": "Адреса трезора", - "vaultNamespaceLabel": "Именски простор", - "vaultOidcMountLabel": "OIDC аутентификација монтирања", - "vaultOidcRoleLabel": "Улога OIDC-а", - "vaultSshMountLabel": "SSH тајне носач", - "vaultSshRoleLabel": "Улога SSH потписника", - "vaultValidPrincipalsLabel": "Важећи принципали", - "vaultKeyTypeLabel": "Тип ефемерног кључа", - "vaultSharedLabel": "Подели са свим корисницима", - "vaultCreateProfile": "Направи профил", - "vaultProfileCreated": "Профил трезора је креиран", - "vaultProfileSaved": "Профил трезора је сачуван", - "vaultProfileDeleted": "Профил трезора је обрисан", - "vaultProfileSaveFailed": "Чување профила у трезору није успело", - "vaultProfileDeleteFailed": "Брисање профила у Трезору није успело", - "vaultSaveProfile": "Сачувај профил", - "vaultProfileValidationError": "Име, адреса трезора и улога SSH потписника су обавезни", - "vaultNoProfiles": "Још нема профила у Трезору.", - "vaultSharedBadge": "дељено", "keyTypeLabel": "Тип кључа", "keyTypeAuto": "Аутоматско откривање", "keyPasteTab": "Залепи", diff --git a/src/ui/locales/translated/sv_SE.json b/src/ui/locales/translated/sv_SE.json index d37a13bcd..426d5f98a 100644 --- a/src/ui/locales/translated/sv_SE.json +++ b/src/ui/locales/translated/sv_SE.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "Välj den metod som Termix kommer att använda för att autentisera denna SSH-anslutning. Andra metoder testas inte automatiskt.", "storedCredential": "Lagrade autentiseringsuppgifter", "selectACredential": "Välj en behörighetsinloggning...", - "vaultProfile": "Profil för valvsignerare", - "selectAVaultProfile": "Välj en Vault-profil...", - "vaultProfileHint": "Inställningarna kommer från den delade profilen; du loggar in på Vault via OIDC när du ansluter. Inga hemligheter lagras.", - "vaultNewProfile": "Ny profil", "secretRefHint": "Du kan klistra in en 1Password-referens (op://vault/item/field) istället för hemligheten; den hämtas från din hemliga källa när du ansluter.", "secretSourcesManage": "Hantera hemliga källor", "secretSourcesTitle": "Hemliga källor", @@ -739,26 +735,6 @@ "secretSourceRequired": "Namn, server-URL och token krävs", "secretSourceSaved": "Hemlig källa sparad", "secretSourceDeleted": "Hemlig källa raderad", - "vaultManageProfiles": "Hantera Vault-profiler", - "vaultAddrLabel": "Valvadress", - "vaultNamespaceLabel": "Namnrymd", - "vaultOidcMountLabel": "OIDC-autentiseringsmontering", - "vaultOidcRoleLabel": "OIDC-roll", - "vaultSshMountLabel": "SSH-hemligheter monteras", - "vaultSshRoleLabel": "SSH-signerarroll", - "vaultValidPrincipalsLabel": "Giltiga principer", - "vaultKeyTypeLabel": "Tillfällig nyckeltyp", - "vaultSharedLabel": "Dela med alla användare", - "vaultCreateProfile": "Skapa profil", - "vaultProfileCreated": "Arkivprofil skapad", - "vaultProfileSaved": "Arkivprofilen har sparats", - "vaultProfileDeleted": "Arkivprofil raderad", - "vaultProfileSaveFailed": "Kunde inte spara arkivprofilen", - "vaultProfileDeleteFailed": "Misslyckades med att ta bort Arkiv-profilen", - "vaultSaveProfile": "Spara profil", - "vaultProfileValidationError": "Namn, valvadress och SSH-signerarroll krävs", - "vaultNoProfiles": "Inga Vault-profiler ännu.", - "vaultSharedBadge": "delad", "keyTypeLabel": "Nyckeltyp", "keyTypeAuto": "Automatisk identifiering", "keyPasteTab": "Klistra", diff --git a/src/ui/locales/translated/th_TH.json b/src/ui/locales/translated/th_TH.json index 1838d2ee2..e19a06bf2 100644 --- a/src/ui/locales/translated/th_TH.json +++ b/src/ui/locales/translated/th_TH.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "เลือกวิธีการตรวจสอบสิทธิ์การเชื่อมต่อ SSH ที่ Termix จะใช้เพียงวิธีเดียวเท่านั้น ระบบจะไม่ลองใช้วิธีการอื่นโดยอัตโนมัติ", "storedCredential": "ข้อมูลประจำตัวที่บันทึกไว้", "selectACredential": "เลือกข้อมูลประจำตัว...", - "vaultProfile": "โปรไฟล์ผู้ลงนาม Vault", - "selectAVaultProfile": "เลือกโปรไฟล์ Vault...", - "vaultProfileHint": "การตั้งค่าต่างๆ มาจากโปรไฟล์ที่ใช้ร่วมกัน คุณจะเข้าสู่ระบบ Vault ผ่าน OIDC เมื่อเชื่อมต่อ ไม่มีข้อมูลลับใดๆ ถูกจัดเก็บไว้", - "vaultNewProfile": "โปรไฟล์ใหม่", "secretRefHint": "คุณสามารถวางข้อมูลอ้างอิง 1Password (op://vault/item/field) แทนรหัสลับได้ โดยระบบจะดึงข้อมูลนี้จากแหล่งข้อมูลลับของคุณเมื่อทำการเชื่อมต่อ", "secretSourcesManage": "จัดการแหล่งข้อมูลลับ", "secretSourcesTitle": "แหล่งข่าวลับ", @@ -739,26 +735,6 @@ "secretSourceRequired": "จำเป็นต้องระบุชื่อ ที่อยู่ URL ของเซิร์ฟเวอร์ และโทเค็น", "secretSourceSaved": "แหล่งข่าวลับถูกบันทึกไว้แล้ว", "secretSourceDeleted": "แหล่งข่าวลับถูกลบแล้ว", - "vaultManageProfiles": "จัดการโปรไฟล์ Vault", - "vaultAddrLabel": "ที่อยู่ห้องนิรภัย", - "vaultNamespaceLabel": "เนมสเปซ", - "vaultOidcMountLabel": "OIDC Auth Mount", - "vaultOidcRoleLabel": "บทบาทของ OIDC", - "vaultSshMountLabel": "SSH Secrets Mount", - "vaultSshRoleLabel": "บทบาทผู้ลงนาม SSH", - "vaultValidPrincipalsLabel": "หลักการที่ถูกต้อง", - "vaultKeyTypeLabel": "ประเภทคีย์ชั่วคราว", - "vaultSharedLabel": "แชร์กับผู้ใช้ทั้งหมด", - "vaultCreateProfile": "สร้างโปรไฟล์", - "vaultProfileCreated": "สร้างโปรไฟล์ Vault แล้ว", - "vaultProfileSaved": "บันทึกโปรไฟล์ Vault แล้ว", - "vaultProfileDeleted": "โปรไฟล์ Vault ถูกลบแล้ว", - "vaultProfileSaveFailed": "ไม่สามารถบันทึกโปรไฟล์ Vault ได้", - "vaultProfileDeleteFailed": "ไม่สามารถลบโปรไฟล์ Vault ได้", - "vaultSaveProfile": "บันทึกโปรไฟล์", - "vaultProfileValidationError": "จำเป็นต้องระบุชื่อ ที่อยู่ Vault และบทบาทผู้ลงนาม SSH", - "vaultNoProfiles": "ยังไม่มีโปรไฟล์ Vault ใดๆ", - "vaultSharedBadge": "แชร์", "keyTypeLabel": "ประเภทกุญแจ", "keyTypeAuto": "ตรวจจับอัตโนมัติ", "keyPasteTab": "แปะ", diff --git a/src/ui/locales/translated/tr_TR.json b/src/ui/locales/translated/tr_TR.json index a8683eb43..343a93353 100644 --- a/src/ui/locales/translated/tr_TR.json +++ b/src/ui/locales/translated/tr_TR.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "Termix'in bu SSH bağlantısını doğrulamak için kullanacağı yöntemi seçin. Diğer yöntemler otomatik olarak denenmez.", "storedCredential": "Kayıtlı Kimlik", "selectACredential": "Bir kimlik seçin...", - "vaultProfile": "Vault İmzalayıcı Profili", - "selectAVaultProfile": "Bir Vault profili seçin...", - "vaultProfileHint": "Ayarlar paylaşılan profilden gelir; bağlandığınızda OIDC ile Vault'ta oturum açarsınız. Hiçbir gizli bilgi saklanmaz.", - "vaultNewProfile": "Yeni profil", "secretRefHint": "Gizli anahtar yerine 1Password referansını (op://vault/item/field) yapıştırabilirsiniz; bağlantı kurulurken bu referans gizli anahtar kaynağınızdan alınır.", "secretSourcesManage": "Gizli kaynakları yönetin", "secretSourcesTitle": "Gizli kaynaklar", @@ -739,26 +735,6 @@ "secretSourceRequired": "İsim, sunucu URL'si ve belirteç (token) gereklidir.", "secretSourceSaved": "Gizli kaynak kaydedildi.", "secretSourceDeleted": "Gizli kaynak silindi", - "vaultManageProfiles": "Vault Profillerini Yönet", - "vaultAddrLabel": "Vault Adresi", - "vaultNamespaceLabel": "Ad Alanı", - "vaultOidcMountLabel": "OIDC Kimlik Doğrulama Bağlama", - "vaultOidcRoleLabel": "OIDC Rolü", - "vaultSshMountLabel": "SSH Gizli Dizin Bağlama", - "vaultSshRoleLabel": "SSH İmzalayıcı Rolü", - "vaultValidPrincipalsLabel": "Geçerli Asıllar", - "vaultKeyTypeLabel": "Geçici Anahtar Türü", - "vaultSharedLabel": "Tüm Kullanıcılarla Paylaş", - "vaultCreateProfile": "Profil oluştur", - "vaultProfileCreated": "Vault profili oluşturuldu", - "vaultProfileSaved": "Vault profili kaydedildi", - "vaultProfileDeleted": "Vault profili silindi", - "vaultProfileSaveFailed": "Vault profili kaydedilemedi", - "vaultProfileDeleteFailed": "Vault profili silinemedi", - "vaultSaveProfile": "Profili kaydet", - "vaultProfileValidationError": "Ad, Vault adresi ve SSH imzalama rolü gereklidir", - "vaultNoProfiles": "Henüz Vault profili yok.", - "vaultSharedBadge": "paylaşılan", "keyTypeLabel": "Anahtar Türü", "keyTypeAuto": "Otomatik Algıla", "keyPasteTab": "Yapıştır", diff --git a/src/ui/locales/translated/uk_UA.json b/src/ui/locales/translated/uk_UA.json index ec62a06c9..6c44a4877 100644 --- a/src/ui/locales/translated/uk_UA.json +++ b/src/ui/locales/translated/uk_UA.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "Виберіть один метод, який Termix використовуватиме для автентифікації цього SSH-з’єднання. Інші методи не перевірятимуться автоматично.", "storedCredential": "Збережені облікові дані", "selectACredential": "Виберіть облікові дані...", - "vaultProfile": "Профіль підписанта сховища", - "selectAVaultProfile": "Виберіть профіль Сховища...", - "vaultProfileHint": "Налаштування надходять зі спільного профілю; ви входите у Сейф через OIDC під час підключення. Секрети не зберігаються.", - "vaultNewProfile": "Новий профіль", "secretRefHint": "Ви можете вставити посилання на 1Password (op://vault/item/field) замість секретного коду; він отримується з вашого секретного джерела під час підключення.", "secretSourcesManage": "Керування секретними джерелами", "secretSourcesTitle": "Секретні джерела", @@ -739,26 +735,6 @@ "secretSourceRequired": "Ім'я, URL-адреса сервера та токен обов'язкові", "secretSourceSaved": "Секретне джерело збережено", "secretSourceDeleted": "Секретне джерело видалено", - "vaultManageProfiles": "Керування профілями Сховища", - "vaultAddrLabel": "Адреса сховища", - "vaultNamespaceLabel": "Простір імен", - "vaultOidcMountLabel": "Монтування автентифікації OIDC", - "vaultOidcRoleLabel": "Роль OIDC", - "vaultSshMountLabel": "SSH-секрети кріплення", - "vaultSshRoleLabel": "Роль підписувача SSH", - "vaultValidPrincipalsLabel": "Дійсні принципали", - "vaultKeyTypeLabel": "Тип тимчасового ключа", - "vaultSharedLabel": "Поділитися з усіма користувачами", - "vaultCreateProfile": "Створити профіль", - "vaultProfileCreated": "Профіль сховища створено", - "vaultProfileSaved": "Профіль сховища збережено", - "vaultProfileDeleted": "Профіль сховища видалено", - "vaultProfileSaveFailed": "Не вдалося зберегти профіль Сейфа", - "vaultProfileDeleteFailed": "Не вдалося видалити профіль Сейфа", - "vaultSaveProfile": "Зберегти профіль", - "vaultProfileValidationError": "Ім'я, адреса сховища та роль підписувача SSH обов'язкові", - "vaultNoProfiles": "Поки що немає профілів Сейфа.", - "vaultSharedBadge": "спільний", "keyTypeLabel": "Тип ключа", "keyTypeAuto": "Автоматичне виявлення", "keyPasteTab": "Вставити", diff --git a/src/ui/locales/translated/vi_VN.json b/src/ui/locales/translated/vi_VN.json index 7f365598c..b935f7488 100644 --- a/src/ui/locales/translated/vi_VN.json +++ b/src/ui/locales/translated/vi_VN.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "Hãy chọn phương thức mà Termix sẽ sử dụng để xác thực kết nối SSH này. Các phương thức khác sẽ không được thử tự động.", "storedCredential": "Thông tin xác thực đã lưu", "selectACredential": "Chọn thông tin xác thực...", - "vaultProfile": "Hồ sơ người ký Vault", - "selectAVaultProfile": "Chọn hồ sơ Vault...", - "vaultProfileHint": "Cài đặt từ hồ sơ dùng chung; bạn sẽ đăng nhập Vault qua OIDC khi kết nối. Không lưu bí mật.", - "vaultNewProfile": "Hồ sơ mới", "secretRefHint": "Bạn có thể dán mã tham chiếu 1Password (op://vault/item/field) thay cho mã bí mật; mã này sẽ được lấy từ nguồn mã bí mật của bạn khi kết nối.", "secretSourcesManage": "Quản lý các nguồn tin mật", "secretSourcesTitle": "Nguồn tin bí mật", @@ -739,26 +735,6 @@ "secretSourceRequired": "Tên người dùng, URL máy chủ và mã thông báo là bắt buộc.", "secretSourceSaved": "Nguồn bí mật đã được lưu", "secretSourceDeleted": "Nguồn tin bí mật đã bị xóa.", - "vaultManageProfiles": "Quản lý hồ sơ Vault", - "vaultAddrLabel": "Địa chỉ Vault", - "vaultNamespaceLabel": "Không gian tên", - "vaultOidcMountLabel": "Điểm gắn xác thực OIDC", - "vaultOidcRoleLabel": "Vai trò OIDC", - "vaultSshMountLabel": "Điểm gắn bí mật SSH", - "vaultSshRoleLabel": "Vai trò ký SSH", - "vaultValidPrincipalsLabel": "Các danh tính hợp lệ", - "vaultKeyTypeLabel": "Loại khóa tạm thời", - "vaultSharedLabel": "Chia sẻ với tất cả người dùng", - "vaultCreateProfile": "Tạo hồ sơ", - "vaultProfileCreated": "Đã tạo hồ sơ Vault", - "vaultProfileSaved": "Đã lưu hồ sơ Vault", - "vaultProfileDeleted": "Đã xóa hồ sơ Vault", - "vaultProfileSaveFailed": "Không thể lưu hồ sơ Vault", - "vaultProfileDeleteFailed": "Không thể xóa hồ sơ Vault", - "vaultSaveProfile": "Lưu hồ sơ", - "vaultProfileValidationError": "Tên, địa chỉ Vault và vai trò ký SSH là bắt buộc", - "vaultNoProfiles": "Chưa có hồ sơ Vault nào.", - "vaultSharedBadge": "được chia sẻ", "keyTypeLabel": "Loại khóa", "keyTypeAuto": "Tự động phát hiện", "keyPasteTab": "Dán", diff --git a/src/ui/locales/translated/zh_CN.json b/src/ui/locales/translated/zh_CN.json index 780d678af..7276eafaa 100644 --- a/src/ui/locales/translated/zh_CN.json +++ b/src/ui/locales/translated/zh_CN.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "请选择 Termix 用于验证此 SSH 连接的一种方法。其他方法不会自动尝试。", "storedCredential": "已存储凭据", "selectACredential": "选择凭据...", - "vaultProfile": "Vault 签名者配置文件", - "selectAVaultProfile": "选择 Vault 配置文件...", - "vaultProfileHint": "设置来自共享配置文件;连接时将使用 OIDC 登录 Vault。不会存储任何机密。", - "vaultNewProfile": "新建配置文件", "secretRefHint": "您可以粘贴 1Password 引用(op://vault/item/field)而不是密钥;连接时会从您的密钥源中获取该引用。", "secretSourcesManage": "管理秘密来源", "secretSourcesTitle": "秘密消息来源", @@ -739,26 +735,6 @@ "secretSourceRequired": "需要提供名称、服务器 URL 和令牌。", "secretSourceSaved": "秘密来源已保存", "secretSourceDeleted": "秘密来源已删除", - "vaultManageProfiles": "管理 Vault 配置文件", - "vaultAddrLabel": "Vault 地址", - "vaultNamespaceLabel": "命名空间", - "vaultOidcMountLabel": "OIDC 认证挂载点", - "vaultOidcRoleLabel": "OIDC 角色", - "vaultSshMountLabel": "SSH 机密挂载点", - "vaultSshRoleLabel": "SSH 签名者角色", - "vaultValidPrincipalsLabel": "有效主体", - "vaultKeyTypeLabel": "临时密钥类型", - "vaultSharedLabel": "与所有用户共享", - "vaultCreateProfile": "创建配置文件", - "vaultProfileCreated": "Vault 配置文件已创建", - "vaultProfileSaved": "Vault 配置文件已保存", - "vaultProfileDeleted": "Vault 配置文件已删除", - "vaultProfileSaveFailed": "保存 Vault 配置文件失败", - "vaultProfileDeleteFailed": "删除 Vault 配置文件失败", - "vaultSaveProfile": "保存配置文件", - "vaultProfileValidationError": "名称、Vault 地址和 SSH 签名者角色为必填项", - "vaultNoProfiles": "暂无 Vault 配置文件。", - "vaultSharedBadge": "共享", "keyTypeLabel": "密钥类型", "keyTypeAuto": "自动检测", "keyPasteTab": "粘贴", diff --git a/src/ui/locales/translated/zh_TW.json b/src/ui/locales/translated/zh_TW.json index 5de1dd566..51873042e 100644 --- a/src/ui/locales/translated/zh_TW.json +++ b/src/ui/locales/translated/zh_TW.json @@ -719,10 +719,6 @@ "authenticationMethodDesc": "請選擇 Termix 用於驗證此 SSH 連線的一種方法。其他方法不會自動嘗試。", "storedCredential": "儲存的憑證", "selectACredential": "選取憑證...", - "vaultProfile": "Vault 簽署者設定檔", - "selectAVaultProfile": "選取 Vault 設定檔...", - "vaultProfileHint": "設定來自共用設定檔;連線時您將透過 OIDC 登入 Vault。不會儲存任何機密。", - "vaultNewProfile": "新增設定檔", "secretRefHint": "您可以貼上 1Password 引用(op://vault/item/field)而不是密鑰;連接時會從您的密鑰來源中獲取該引用。", "secretSourcesManage": "管理秘密來源", "secretSourcesTitle": "秘密消息來源", @@ -739,26 +735,6 @@ "secretSourceRequired": "需要提供名稱、伺服器 URL 和令牌。", "secretSourceSaved": "秘密來源已儲存", "secretSourceDeleted": "秘密來源已刪除", - "vaultManageProfiles": "管理 Vault 設定檔", - "vaultAddrLabel": "Vault 位址", - "vaultNamespaceLabel": "命名空間", - "vaultOidcMountLabel": "OIDC 驗證掛載", - "vaultOidcRoleLabel": "OIDC 角色", - "vaultSshMountLabel": "SSH 機密掛載", - "vaultSshRoleLabel": "SSH 簽署者角色", - "vaultValidPrincipalsLabel": "有效主體", - "vaultKeyTypeLabel": "暫時金鑰類型", - "vaultSharedLabel": "與所有使用者共用", - "vaultCreateProfile": "建立設定檔", - "vaultProfileCreated": "Vault 設定檔已建立", - "vaultProfileSaved": "Vault 設定檔已儲存", - "vaultProfileDeleted": "Vault 設定檔已刪除", - "vaultProfileSaveFailed": "無法儲存 Vault 設定檔", - "vaultProfileDeleteFailed": "無法刪除 Vault 設定檔", - "vaultSaveProfile": "儲存設定檔", - "vaultProfileValidationError": "名稱、Vault 位址和 SSH 簽署者角色為必填", - "vaultNoProfiles": "尚無 Vault 設定檔。", - "vaultSharedBadge": "已共用", "keyTypeLabel": "金鑰類型", "keyTypeAuto": "自動偵測", "keyPasteTab": "貼上", diff --git a/src/ui/main-axios.ts b/src/ui/main-axios.ts index 371328604..a72603fe8 100644 --- a/src/ui/main-axios.ts +++ b/src/ui/main-axios.ts @@ -1783,14 +1783,6 @@ export { deployCredentialToHost, } from "@/api/credentials-api"; -export { - getVaultProfiles, - createVaultProfile, - updateVaultProfile, - deleteVaultProfile, - type VaultProfilePayload, -} from "@/api/vault-profiles-api"; - // ============================================================================ // SNIPPETS API // ============================================================================ diff --git a/src/ui/sidebar/HostEditor.tsx b/src/ui/sidebar/HostEditor.tsx index d06535cfe..634a6651b 100644 --- a/src/ui/sidebar/HostEditor.tsx +++ b/src/ui/sidebar/HostEditor.tsx @@ -41,7 +41,6 @@ import { updateSSHHost, getSnippets, getUserInfo, - getVaultProfiles, getHostPassword, adminCreateUserHost, adminUpdateUserHost, @@ -57,7 +56,7 @@ import { parseCustomThemes, type SavedCustomTheme, } from "@/api/open-tabs-api"; -import type { Host, VaultProfile } from "@/types/ui-types"; +import type { Host } from "@/types/ui-types"; import type { SSHHost } from "@/types"; import { useTabsSafe } from "@/shell/TabContext"; import { updatePluginHostSettings } from "@/api/plugins-api"; @@ -78,7 +77,6 @@ import { useConnectionDefaults } from "@/contexts/ConnectionDefaultsContext"; import { HostEditorGeneralTab } from "./HostEditorGeneralTab"; import { withProtocolSettings } from "./host-protocols"; import { canEditHost } from "./host-permissions"; -import { VaultProfileManager } from "./VaultProfileManager"; import { getHostEditorSection, isSshGroupTab, @@ -167,8 +165,6 @@ export function HostEditor({ const [saving, setSaving] = useState(false); const [snippets, setSnippets] = useState<{ id: number; name: string }[]>([]); const [isOidcUser, setIsOidcUser] = useState(false); - const [vaultProfiles, setVaultProfiles] = useState([]); - const [showVaultManager, setShowVaultManager] = useState(false); const [showSecretSources, setShowSecretSources] = useState(false); const [quickCredentialName, setQuickCredentialName] = useState(""); const [creatingQuickCredential, setCreatingQuickCredential] = useState(false); @@ -232,12 +228,6 @@ export function HostEditor({ setField("customThemeColors", { ...theme.colors }); }; - const reloadVaultProfiles = () => { - getVaultProfiles() - .then((res) => setVaultProfiles(res as unknown as VaultProfile[])) - .catch(() => {}); - }; - useEffect(() => { getUserInfo() .then((info) => setIsOidcUser(info.is_oidc)) @@ -251,7 +241,6 @@ export function HostEditor({ loadSnippets .then((res) => setSnippets(mapSnippetResponse(res))) .catch(() => {}); - reloadVaultProfiles(); }, [adminTargetUserId]); useEffect(() => { @@ -465,7 +454,7 @@ export function HostEditor({ }; // Shared hosts: view-level recipients see a read-only editor; edit-level - // recipients may change the host but never its credential/vault references + // recipients may change the host but never its credential references // or auth type (owner-only, enforced server-side too). const isSharedHost = !!host?.isShared; const readOnly = isSharedHost && host !== null && !canEditHost(host); @@ -873,58 +862,6 @@ export function HostEditor({
)} - {authMethod === "vault" && ( -
- - - setField("vaultProfileId", e.target.value) - } - className="flex h-9 w-full border border-border bg-background px-3 py-1 text-xs outline-none focus:ring-1 focus:ring-ring" - > - - {vaultProfiles.map((p) => ( - - ))} - -
-

- {t("hosts.vaultProfileHint")} -

-
- - {t("hosts.docsLink")} - - -
-
-
- )} - {authMethod === "vault" && showVaultManager && ( - setShowVaultManager(false)} - /> - )} {authMethod === "credential" && ( <>
diff --git a/src/ui/sidebar/HostEditorData.ts b/src/ui/sidebar/HostEditorData.ts index 147050a5e..ad9cf9d03 100644 --- a/src/ui/sidebar/HostEditorData.ts +++ b/src/ui/sidebar/HostEditorData.ts @@ -134,7 +134,6 @@ export function createHostEditorForm( : d?.credentialId != null ? String(d.credentialId) : "", - vaultProfileId: host?.vaultProfileId ?? "", overrideCredentialUsername: host?.overrideCredentialUsername ?? false, folder: host?.folder ?? "", parentHostId: host?.parentHostId ?? "", @@ -294,7 +293,6 @@ export function omitOwnerSshAuthFromSharedEdit( keyType: _keyType, sudoPassword: _sudoPassword, credentialId: _credentialId, - vaultProfileId: _vaultProfileId, overrideCredentialUsername: _overrideCredentialUsername, shareSshAuth: _shareSshAuth, ...editableFields @@ -325,7 +323,6 @@ export function buildHostEditorPayload( const usesKey = form.authType === "key"; const usesPassword = form.authType === "password"; const usesAgent = form.authType === "agent"; - const usesVault = form.authType === "vault"; // With SSH off, the host's primary protocol is the first plugin protocol // switched on, and its port stands in for the host port. const primaryProtocol = protocols.enableSsh @@ -410,8 +407,6 @@ export function buildHostEditorPayload( keyType: usesKey && form.keyType !== "auto" ? form.keyType : null, credentialId: usesCredential && form.credentialId ? Number(form.credentialId) : null, - vaultProfileId: - usesVault && form.vaultProfileId ? Number(form.vaultProfileId) : null, overrideCredentialUsername: form.overrideCredentialUsername, notes: form.notes, enableTerminal: form.enableTerminal, diff --git a/src/ui/sidebar/HostManagerData.ts b/src/ui/sidebar/HostManagerData.ts index aed5b8a24..d0fd96de7 100644 --- a/src/ui/sidebar/HostManagerData.ts +++ b/src/ui/sidebar/HostManagerData.ts @@ -78,10 +78,6 @@ export function sshHostToHost(h: SSHHostWithStatus): Host { keyPassword: h.keyPassword, keyType: h.keyType, credentialId: h.credentialId != null ? String(h.credentialId) : undefined, - vaultProfileId: - (h as { vaultProfileId?: number | string | null }).vaultProfileId != null - ? String((h as { vaultProfileId?: number | string }).vaultProfileId) - : undefined, notes: h.notes, pin: h.pin ?? false, sortOrder: h.sortOrder ?? null, diff --git a/src/ui/tests/sidebar/HostEditorData.test.ts b/src/ui/tests/sidebar/HostEditorData.test.ts index 8eb3ccfa8..eec0c2c53 100644 --- a/src/ui/tests/sidebar/HostEditorData.test.ts +++ b/src/ui/tests/sidebar/HostEditorData.test.ts @@ -50,7 +50,6 @@ describe("omitOwnerSshAuthFromSharedEdit", () => { key: "owner-key", keyPassword: "owner-passphrase", keyType: "ssh-ed25519", - vaultProfileId: "9", overrideCredentialUsername: true, shareSshAuth: true, sudoPassword: "owner-sudo", @@ -70,7 +69,6 @@ describe("omitOwnerSshAuthFromSharedEdit", () => { for (const field of [ "authType", "credentialId", - "vaultProfileId", "overrideCredentialUsername", "shareSshAuth", "password", @@ -128,32 +126,6 @@ describe("buildHostEditorPayload auth field isolation", () => { expect(payload.password).toBe("newpass"); }); - it("drops the vaultProfileId when switching a host away from vault auth", () => { - const form = { - ...createHostEditorForm(null), - authType: "password" as const, - password: "newpass", - vaultProfileId: "9", - }; - - const payload = buildHostEditorPayload(form, sshOnly); - - expect(payload.vaultProfileId).toBeNull(); - expect(payload.password).toBe("newpass"); - }); - - it("sends vaultProfileId when authType is vault", () => { - const form = { - ...createHostEditorForm(null), - authType: "vault" as const, - vaultProfileId: "9", - }; - - const payload = buildHostEditorPayload(form, sshOnly); - - expect(payload.vaultProfileId).toBe(9); - }); - it("sends credentialId and optional password when authType is credential", () => { const form = { ...createHostEditorForm(null),