diff --git a/backend/migrations/20221127093847_add_account_error.down.sql b/backend/migrations/20221127093847_add_account_error.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20221127093847_add_account_error.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20221127093847_add_account_error.up.sql b/backend/migrations/20221127093847_add_account_error.up.sql new file mode 100644 index 0000000000..92393c7fd3 --- /dev/null +++ b/backend/migrations/20221127093847_add_account_error.up.sql @@ -0,0 +1,2 @@ +-- Add up migration script here +ALTER TABLE account ADD COLUMN refresh_error TEXT; diff --git a/backend/sqlx-data.json b/backend/sqlx-data.json index f9911357ab..777c7f13b5 100644 --- a/backend/sqlx-data.json +++ b/backend/sqlx-data.json @@ -146,6 +146,20 @@ }, "query": "DELETE FROM variable WHERE path = $1 AND workspace_id = $2" }, + "09e2a19435068f9e9bfd5bcb44b4e283c71729f81550f6f7156ce4970345cc07": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Text", + "Text", + "Int4" + ] + } + }, + "query": "UPDATE account SET refresh_error = $1 WHERE workspace_id = $2 AND id = $3" + }, "0a7212dd507ed8f7a311724185e39ecc1809abb208a681ad711614c27baadd83": { "describe": { "columns": [ diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index b87c858011..1c8a16fd40 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -4128,6 +4128,10 @@ components: type: object additionalProperties: type: boolean + is_expired: + type: boolean + refresh_error: + type: string required: - workspace_id - path diff --git a/backend/windmill-api/src/oauth2.rs b/backend/windmill-api/src/oauth2.rs index ce9216e7a5..d890d9fc31 100644 --- a/backend/windmill-api/src/oauth2.rs +++ b/backend/windmill-api/src/oauth2.rs @@ -509,18 +509,25 @@ pub async fn _refresh_token<'c>( .client) .to_owned(); - let token_json = client - .exchange_refresh_token(&RefreshToken::from(account.refresh_token.clone())) - .with_client(&http_client) - .execute::() - .await - .map_err(to_anyhow)?; + let token = _exchange_token(client, &account.refresh_token, http_client).await; - let token = serde_json::from_value::(token_json.clone()).map_err(|e| { - Error::BadConfig(format!( - "Error deserializing response as a new token: {e}\nresponse:{token_json}" - )) - })?; + if let Err(token_err) = token { + sqlx::query!( + "UPDATE account SET refresh_error = $1 WHERE workspace_id = $2 AND id = $3", + token_err.to_string(), + w_id, + id, + ) + .execute(&mut tx) + .await?; + tx.commit().await?; + return Err(error::Error::BadRequest(format!( + "Error refreshing token: {:#?}", + token_err + ))); + }; + + let token = token.unwrap(); let expires_at = now_from_db(&mut tx).await? + chrono::Duration::seconds( @@ -560,6 +567,25 @@ pub async fn _refresh_token<'c>( Ok(token_str) } +async fn _exchange_token( + client: OClient, + refresh_token: &str, + http_client: Client, +) -> Result { + let token_json = client + .exchange_refresh_token(&RefreshToken::from(refresh_token.clone())) + .with_client(&http_client) + .execute::() + .await + .map_err(to_anyhow)?; + let token = serde_json::from_value::(token_json.clone()).map_err(|e| { + Error::BadConfig(format!( + "Error deserializing response as a new token: {e}\nresponse:{token_json}" + )) + })?; + Ok(token) +} + #[derive(Deserialize)] pub struct OAuthCallback { code: String, diff --git a/backend/windmill-api/src/variables.rs b/backend/windmill-api/src/variables.rs index e6ac7d2109..148899f2cd 100644 --- a/backend/windmill-api/src/variables.rs +++ b/backend/windmill-api/src/variables.rs @@ -82,11 +82,11 @@ async fn list_variables( let mut tx = user_db.begin(&authed).await?; let rows = sqlx::query_as::<_, ListableVariable>( - "SELECT workspace_id, path, CASE WHEN is_secret IS TRUE THEN null ELSE value::text END as \ - value, is_secret, description, extra_perms, account, is_oauth, false as is_expired from \ - variable - WHERE (workspace_id = $1 OR (is_secret IS NOT TRUE AND workspace_id = 'starter')) ORDER \ - BY path", + "SELECT variable.workspace_id, path, CASE WHEN is_secret IS TRUE THEN null ELSE value::text END as value, + is_secret, description, extra_perms, account, is_oauth, (now() > account.expires_at) as is_expired, + account.refresh_error from variable + LEFT JOIN account ON variable.account = account.id AND account.workspace_id = variable.workspace_id + WHERE variable.workspace_id = $1 OR (is_secret IS NOT TRUE AND variable.workspace_id = 'starter') ORDER BY path", ) .bind(&w_id) .fetch_all(&mut tx) @@ -113,7 +113,7 @@ async fn get_variable( let mut tx = user_db.begin(&authed).await?; let variable_o = sqlx::query_as::<_, ListableVariable>( - "SELECT variable.*, (now() > account.expires_at) as is_expired from variable + "SELECT variable.*, (now() > account.expires_at) as is_expired, account.refresh_error from variable LEFT JOIN account ON variable.account = account.id WHERE variable.path = $1 AND (variable.workspace_id = $2 OR (is_secret IS NOT TRUE AND \ variable.workspace_id = 'starter')) diff --git a/backend/windmill-common/src/variables.rs b/backend/windmill-common/src/variables.rs index 9a760c3b60..a0f6b5f534 100644 --- a/backend/windmill-common/src/variables.rs +++ b/backend/windmill-common/src/variables.rs @@ -29,6 +29,7 @@ pub struct ListableVariable { pub account: Option, pub is_oauth: bool, pub is_expired: Option, + pub refresh_error: Option, } #[derive(Deserialize)] diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 32f51f1752..42e81bdfaf 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -21,6 +21,7 @@ "monaco-editor": "^0.34.1", "monaco-editor-workers": "^0.34.2", "monaco-languageclient": "4.0.1", + "monaco-yaml": "^4.0.2", "set-interval-async": "^3.0.2", "svelte-autosize": "^1.0.1", "svelte-chartjs": "^3.1.0", @@ -546,8 +547,7 @@ "node_modules/@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" }, "node_modules/@types/marked": { "version": "4.0.3", @@ -3626,6 +3626,11 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -4006,6 +4011,56 @@ "npm": ">=8.0.0" } }, + "node_modules/monaco-marker-data-provider": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/monaco-marker-data-provider/-/monaco-marker-data-provider-1.1.1.tgz", + "integrity": "sha512-PGB7TJSZE5tmHzkxv/OEwK2RGNC2A7dcq4JRJnnj31CUAsfmw0Gl+1QTrH0W0deKhcQmQM0YVPaqgQ+0wCt8Mg==", + "funding": { + "url": "https://github.com/sponsors/remcohaszing" + }, + "peerDependencies": { + "monaco-editor": ">=0.30.0" + } + }, + "node_modules/monaco-worker-manager": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/monaco-worker-manager/-/monaco-worker-manager-2.0.1.tgz", + "integrity": "sha512-kdPL0yvg5qjhKPNVjJoym331PY/5JC11aPJXtCZNwWRvBr6jhkIamvYAyiY5P1AWFmNOy0aRDRoMdZfa71h8kg==", + "peerDependencies": { + "monaco-editor": ">=0.30.0" + } + }, + "node_modules/monaco-yaml": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/monaco-yaml/-/monaco-yaml-4.0.2.tgz", + "integrity": "sha512-Wxn6CblkQDLOUusfi0eZ3qZhkuKYIrK7fXlkJOOG+W18zgKePbuZW0XNWpczlxDC27D753dB18pMnx4U7MZ3yg==", + "dependencies": { + "@types/json-schema": "^7.0.0", + "jsonc-parser": "^3.0.0", + "monaco-marker-data-provider": "^1.0.0", + "monaco-worker-manager": "^2.0.0", + "path-browserify": "^1.0.0", + "prettier": "^2.0.0", + "vscode-languageserver-textdocument": "^1.0.0", + "vscode-languageserver-types": "^3.0.0", + "vscode-uri": "^3.0.0", + "yaml": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/remcohaszing" + }, + "peerDependencies": { + "monaco-editor": ">=0.30" + } + }, + "node_modules/monaco-yaml/node_modules/yaml": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.3.tgz", + "integrity": "sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==", + "engines": { + "node": ">= 14" + } + }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -4301,8 +4356,7 @@ "node_modules/path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dev": true + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==" }, "node_modules/path-exists": { "version": "4.0.0", @@ -4984,7 +5038,6 @@ "version": "2.7.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", - "dev": true, "bin": { "prettier": "bin-prettier.js" }, @@ -6678,6 +6731,11 @@ "vscode-languageserver-types": "3.17.2" } }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.7.tgz", + "integrity": "sha512-bFJH7UQxlXT8kKeyiyu41r22jCZXG8kuuVVA33OEJn1diWOZK5n8zBSPZFHVBOu8kXZ6h0LIRhf5UnCo61J4Hg==" + }, "node_modules/vscode-languageserver-types": { "version": "3.17.2", "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz", @@ -6695,6 +6753,11 @@ "integrity": "sha512-OkE/mYm1h5ZX9IEKeKR/2zKDt2SzYyIfTEOVFX4QhA+B3BPROvNEmDDXvBThz3qknKO3Cy/VVb8/sx1UlqP/Xw==", "peer": true }, + "node_modules/vscode-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.6.tgz", + "integrity": "sha512-fmL7V1eiDBFRRnu+gfRWTzyPpNIHJTc4mWnFkwBUmO9U3KPgJAmTx7oxi2bl/Rh6HLdU7+4C9wlj0k2E4AdKFQ==" + }, "node_modules/vscode-ws-jsonrpc": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/vscode-ws-jsonrpc/-/vscode-ws-jsonrpc-2.0.0.tgz", @@ -7308,8 +7371,7 @@ "@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" }, "@types/marked": { "version": "4.0.3", @@ -9453,6 +9515,11 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" + }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -9747,6 +9814,42 @@ "vscode-languageclient": "8.0.2" } }, + "monaco-marker-data-provider": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/monaco-marker-data-provider/-/monaco-marker-data-provider-1.1.1.tgz", + "integrity": "sha512-PGB7TJSZE5tmHzkxv/OEwK2RGNC2A7dcq4JRJnnj31CUAsfmw0Gl+1QTrH0W0deKhcQmQM0YVPaqgQ+0wCt8Mg==", + "requires": {} + }, + "monaco-worker-manager": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/monaco-worker-manager/-/monaco-worker-manager-2.0.1.tgz", + "integrity": "sha512-kdPL0yvg5qjhKPNVjJoym331PY/5JC11aPJXtCZNwWRvBr6jhkIamvYAyiY5P1AWFmNOy0aRDRoMdZfa71h8kg==", + "requires": {} + }, + "monaco-yaml": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/monaco-yaml/-/monaco-yaml-4.0.2.tgz", + "integrity": "sha512-Wxn6CblkQDLOUusfi0eZ3qZhkuKYIrK7fXlkJOOG+W18zgKePbuZW0XNWpczlxDC27D753dB18pMnx4U7MZ3yg==", + "requires": { + "@types/json-schema": "^7.0.0", + "jsonc-parser": "^3.0.0", + "monaco-marker-data-provider": "^1.0.0", + "monaco-worker-manager": "^2.0.0", + "path-browserify": "^1.0.0", + "prettier": "^2.0.0", + "vscode-languageserver-textdocument": "^1.0.0", + "vscode-languageserver-types": "^3.0.0", + "vscode-uri": "^3.0.0", + "yaml": "^2.0.0" + }, + "dependencies": { + "yaml": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.3.tgz", + "integrity": "sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==" + } + } + }, "mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -9967,8 +10070,7 @@ "path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dev": true + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==" }, "path-exists": { "version": "4.0.0", @@ -10394,8 +10496,7 @@ "prettier": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", - "dev": true + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==" }, "prettier-plugin-svelte": { "version": "2.8.0", @@ -11625,6 +11726,11 @@ "vscode-languageserver-types": "3.17.2" } }, + "vscode-languageserver-textdocument": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.7.tgz", + "integrity": "sha512-bFJH7UQxlXT8kKeyiyu41r22jCZXG8kuuVVA33OEJn1diWOZK5n8zBSPZFHVBOu8kXZ6h0LIRhf5UnCo61J4Hg==" + }, "vscode-languageserver-types": { "version": "3.17.2", "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz", @@ -11642,6 +11748,11 @@ "integrity": "sha512-OkE/mYm1h5ZX9IEKeKR/2zKDt2SzYyIfTEOVFX4QhA+B3BPROvNEmDDXvBThz3qknKO3Cy/VVb8/sx1UlqP/Xw==", "peer": true }, + "vscode-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.6.tgz", + "integrity": "sha512-fmL7V1eiDBFRRnu+gfRWTzyPpNIHJTc4mWnFkwBUmO9U3KPgJAmTx7oxi2bl/Rh6HLdU7+4C9wlj0k2E4AdKFQ==" + }, "vscode-ws-jsonrpc": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/vscode-ws-jsonrpc/-/vscode-ws-jsonrpc-2.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 8b17e0a818..fd3c09c774 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -71,6 +71,7 @@ "monaco-editor": "^0.34.1", "monaco-editor-workers": "^0.34.2", "monaco-languageclient": "4.0.1", + "monaco-yaml": "^4.0.2", "set-interval-async": "^3.0.2", "svelte-autosize": "^1.0.1", "svelte-chartjs": "^3.1.0", diff --git a/frontend/src/lib/components/SimpleEditor.svelte b/frontend/src/lib/components/SimpleEditor.svelte index ebcce55f87..7cf0ddaa6d 100644 --- a/frontend/src/lib/components/SimpleEditor.svelte +++ b/frontend/src/lib/components/SimpleEditor.svelte @@ -20,6 +20,7 @@ import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker' import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker' + import yamlWorker from 'monaco-yaml/yaml.worker?worker' import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker' import { buildWorkerDefinition } from 'monaco-editor-workers' @@ -58,6 +59,8 @@ getWorker: function (_moduleId: any, label: string) { if (label === 'json') { return new jsonWorker() + } else if (label === 'yaml') { + return new yamlWorker() } else if (label === 'typescript' || label === 'javascript') { return new tsWorker() } else { diff --git a/frontend/src/lib/components/VariableEditor.svelte b/frontend/src/lib/components/VariableEditor.svelte index c58132051f..849e9e09cb 100644 --- a/frontend/src/lib/components/VariableEditor.svelte +++ b/frontend/src/lib/components/VariableEditor.svelte @@ -6,12 +6,14 @@ import { workspaceStore } from '$lib/stores' import Required from './Required.svelte' import Tooltip from './Tooltip.svelte' - import { Button } from './common' + import { Button, ToggleButton, ToggleButtonGroup } from './common' import Drawer from './common/drawer/Drawer.svelte' import DrawerContent from './common/drawer/DrawerContent.svelte' import Alert from './common/alert/Alert.svelte' import autosize from 'svelte-autosize' import Toggle from './Toggle.svelte' + import { faSave } from '@fortawesome/free-solid-svg-icons' + import SimpleEditor from './SimpleEditor.svelte' const dispatch = createEventDispatcher() @@ -114,9 +116,10 @@ sendUserToast(`Could not update variable: ${err.body}`, true) } } + let editorKind: 'plain' | 'json' | 'yaml' = 'plain' - + {#if variable.is_secret} - - If the variable is a secret, you will not be able to read the value of it from the - variable editor UI but only within scripts. - - Within scripts, every read of the value create the audit log: - 'variables.decrypt_secret' - + + Every secret is encrypted at rest and in transit with a key specific to this + workspace. In addition, any read of a secret variable generates an audit log whose + operation name is: variables.decrypt_secret {/if} - +
Variable value @@ -165,17 +155,34 @@ {#if edit && variable.is_secret} loadVariable(initialPath)}>Load secret value{/if}
-