diff --git a/package.json b/package.json index 4d5bf6d4..898fcfa8 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "packages/plugin-runtime", "packages/plugin-runtime-types", "packages/common-lib", + "plugins/auth-apikey", "plugins/auth-basic", "plugins/auth-bearer", "plugins/auth-jwt", diff --git a/packages/plugin-runtime-types/src/bindings/gen_events.ts b/packages/plugin-runtime-types/src/bindings/gen_events.ts index 38972263..31f17224 100644 --- a/packages/plugin-runtime-types/src/bindings/gen_events.ts +++ b/packages/plugin-runtime-types/src/bindings/gen_events.ts @@ -21,7 +21,12 @@ export type CallHttpAuthenticationResponse = { * HTTP headers to add to the request. Existing headers will be replaced, while * new headers will be added. */ -setHeaders: Array, }; +setHeaders?: Array, +/** + * Query parameters to add to the request. Existing params will be replaced, while + * new params will be added. + */ +setQueryParameters?: Array, }; export type CallHttpRequestActionArgs = { httpRequest: HttpRequest, }; diff --git a/packages/plugin-runtime/src/PluginInstance.ts b/packages/plugin-runtime/src/PluginInstance.ts index 062088f1..7bebe633 100644 --- a/packages/plugin-runtime/src/PluginInstance.ts +++ b/packages/plugin-runtime/src/PluginInstance.ts @@ -253,12 +253,11 @@ export class PluginInstance { const auth = this.#mod.authentication; if (typeof auth?.onApply === 'function') { applyFormInputDefaults(auth.args, payload.values); - const result = await auth.onApply(ctx, payload); this.#sendPayload( windowContext, { type: 'call_http_authentication_response', - setHeaders: result.setHeaders, + ...(await auth.onApply(ctx, payload)), }, replyId, ); @@ -579,7 +578,7 @@ export class PluginInstance { event.windowContext, payload, ); - return result.data; + return result.data as any; }, }, store: { diff --git a/plugins/auth-apikey/package.json b/plugins/auth-apikey/package.json new file mode 100644 index 00000000..7e154198 --- /dev/null +++ b/plugins/auth-apikey/package.json @@ -0,0 +1,17 @@ +{ + "name": "@yaak/auth-apikey", + "displayName": "API Key Authentication", + "description": "Authenticate requests using an API key", + "repository": { + "type": "git", + "url": "https://github.com/mountain-loop/yaak.git", + "directory": "plugins/auth-apikey" + }, + "private": true, + "version": "0.1.0", + "scripts": { + "build": "yaakcli build", + "dev": "yaakcli dev", + "lint": "eslint . --ext .ts,.tsx" + } +} diff --git a/plugins/auth-apikey/src/index.ts b/plugins/auth-apikey/src/index.ts new file mode 100644 index 00000000..fef90bda --- /dev/null +++ b/plugins/auth-apikey/src/index.ts @@ -0,0 +1,53 @@ +import type { PluginDefinition } from '@yaakapp/api'; + +export const plugin: PluginDefinition = { + authentication: { + name: 'apikey', + label: 'API Key', + shortLabel: 'API Key', + args: [ + { + type: 'select', + name: 'location', + label: 'Behavior', + defaultValue: 'header', + options: [ + { label: 'Insert Header', value: 'header' }, + { label: 'Append Query Parameter', value: 'query' }, + ], + }, + { + type: 'text', + name: 'key', + label: 'Key', + dynamic: (_ctx, { values }) => { + return values.location === 'query' ? { + label: 'Parameter Name', + description: 'The name of the query parameter to add to the request', + } : { + label: 'Header Name', + description: 'The name of the header to add to the request', + }; + }, + }, + { + type: 'text', + name: 'value', + label: 'API Key', + optional: true, + password: true, + }, + ], + async onApply(_ctx, { values }) { + const key = String(values.key ?? ''); + const value = String(values.value ?? ''); + const location = String(values.location); + + if (location === 'query') { + return { setQueryParameters: [{ name: key, value }] }; + } else { + return { setHeaders: [{ name: key, value }] }; + } + }, + }, +}; diff --git a/plugins/auth-apikey/tsconfig.json b/plugins/auth-apikey/tsconfig.json new file mode 100644 index 00000000..4082f16a --- /dev/null +++ b/plugins/auth-apikey/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json" +} diff --git a/src-tauri/src/grpc.rs b/src-tauri/src/grpc.rs index 4c98c4ab..84bbe3cd 100644 --- a/src-tauri/src/grpc.rs +++ b/src-tauri/src/grpc.rs @@ -69,7 +69,7 @@ pub(crate) async fn build_metadata( let auth = request.authentication.clone(); let plugin_req = CallHttpAuthenticationRequest { context_id: format!("{:x}", md5::compute(authentication_context_id)), - values: serde_json::from_value(serde_json::to_value(&auth).unwrap()).unwrap(), + values: serde_json::from_value(serde_json::to_value(&auth)?)?, method: "POST".to_string(), url: request.url.clone(), headers: metadata @@ -83,7 +83,7 @@ pub(crate) async fn build_metadata( let plugin_result = plugin_manager .call_http_authentication(&window, &authentication_type, plugin_req) .await?; - for header in plugin_result.set_headers { + for header in plugin_result.set_headers.unwrap_or_default() { metadata.insert(header.name, header.value); } } diff --git a/src-tauri/src/http_request.rs b/src-tauri/src/http_request.rs index 8f86d36b..fae454e3 100644 --- a/src-tauri/src/http_request.rs +++ b/src-tauri/src/http_request.rs @@ -452,10 +452,7 @@ pub async fn send_http_request( Some(authentication_type) => { let req = CallHttpAuthenticationRequest { context_id: format!("{:x}", md5::compute(auth_context_id)), - values: serde_json::from_value( - serde_json::to_value(&request.authentication).unwrap(), - ) - .unwrap(), + values: serde_json::from_value(serde_json::to_value(&request.authentication)?)?, url: sendable_req.url().to_string(), method: sendable_req.method().to_string(), headers: sendable_req @@ -482,11 +479,19 @@ pub async fn send_http_request( }; let headers = sendable_req.headers_mut(); - for header in plugin_result.set_headers { - headers.insert( - HeaderName::from_str(&header.name).unwrap(), - HeaderValue::from_str(&header.value).unwrap(), - ); + for header in plugin_result.set_headers.unwrap_or_default() { + match (HeaderName::from_str(&header.name), HeaderValue::from_str(&header.value)) { + (Ok(name), Ok(value)) => { + headers.insert(name, value); + } + _ => continue, + }; + } + + let mut query_pairs = sendable_req.url_mut().query_pairs_mut(); + for p in plugin_result.set_query_parameters.unwrap_or_default() { + println!("Adding query parameter: {:?}", p); + query_pairs.append_pair(&p.name, &p.value); } } } diff --git a/src-tauri/yaak-plugins/bindings/gen_events.ts b/src-tauri/yaak-plugins/bindings/gen_events.ts index 38972263..31f17224 100644 --- a/src-tauri/yaak-plugins/bindings/gen_events.ts +++ b/src-tauri/yaak-plugins/bindings/gen_events.ts @@ -21,7 +21,12 @@ export type CallHttpAuthenticationResponse = { * HTTP headers to add to the request. Existing headers will be replaced, while * new headers will be added. */ -setHeaders: Array, }; +setHeaders?: Array, +/** + * Query parameters to add to the request. Existing params will be replaced, while + * new params will be added. + */ +setQueryParameters?: Array, }; export type CallHttpRequestActionArgs = { httpRequest: HttpRequest, }; diff --git a/src-tauri/yaak-plugins/src/events.rs b/src-tauri/yaak-plugins/src/events.rs index de2057fd..30fd1152 100644 --- a/src-tauri/yaak-plugins/src/events.rs +++ b/src-tauri/yaak-plugins/src/events.rs @@ -649,7 +649,13 @@ pub enum JsonPrimitive { pub struct CallHttpAuthenticationResponse { /// HTTP headers to add to the request. Existing headers will be replaced, while /// new headers will be added. - pub set_headers: Vec, + #[ts(optional)] + pub set_headers: Option>, + + /// Query parameters to add to the request. Existing params will be replaced, while + /// new params will be added. + #[ts(optional)] + pub set_query_parameters: Option>, } #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] diff --git a/src-tauri/yaak-plugins/src/manager.rs b/src-tauri/yaak-plugins/src/manager.rs index 4484c1e8..e8231c90 100644 --- a/src-tauri/yaak-plugins/src/manager.rs +++ b/src-tauri/yaak-plugins/src/manager.rs @@ -643,7 +643,8 @@ impl PluginManager { if disabled { info!("Not applying disabled auth {:?}", auth_name); return Ok(CallHttpAuthenticationResponse { - set_headers: Vec::new(), + set_headers: None, + set_query_parameters: None }); } diff --git a/src-tauri/yaak-ws/src/commands.rs b/src-tauri/yaak-ws/src/commands.rs index 29e22499..c1a3a0a2 100644 --- a/src-tauri/yaak-ws/src/commands.rs +++ b/src-tauri/yaak-ws/src/commands.rs @@ -212,6 +212,35 @@ pub(crate) async fn connect( ) .await?; + let connection = app_handle.db().upsert_websocket_connection( + &WebsocketConnection { + workspace_id: request.workspace_id.clone(), + request_id: request_id.to_string(), + ..Default::default() + }, + &UpdateSource::from_window(&window), + )?; + + let (mut url, url_parameters) = apply_path_placeholders(&request.url, request.url_parameters); + if !url.starts_with("ws://") && !url.starts_with("wss://") { + url.insert_str(0, "ws://"); + } + + // Add URL parameters to URL + let mut url = match Url::parse(&url) { + Ok(url) => url, + Err(e) => { + return Ok(app_handle.db().upsert_websocket_connection( + &WebsocketConnection { + error: Some(format!("Failed to parse URL {}", e.to_string())), + state: WebsocketConnectionState::Closed, + ..connection + }, + &UpdateSource::from_window(&window), + )?); + } + }; + let mut headers = HeaderMap::new(); for h in request.headers.clone() { @@ -256,11 +285,17 @@ pub(crate) async fn connect( let plugin_result = plugin_manager .call_http_authentication(&window, &authentication_type, plugin_req) .await?; - for header in plugin_result.set_headers { - headers.insert( - HeaderName::from_str(&header.name).unwrap(), - HeaderValue::from_str(&header.value).unwrap(), - ); + for header in plugin_result.set_headers.unwrap_or_default() { + match (HeaderName::from_str(&header.name), HeaderValue::from_str(&header.value)) { + (Ok(name), Ok(value)) => { + headers.insert(name, value); + } + _ => continue, + }; + } + let mut query_pairs = url.query_pairs_mut(); + for p in plugin_result.set_query_parameters.unwrap_or_default() { + query_pairs.append_pair(&p.name, &p.value); } } } @@ -271,38 +306,9 @@ pub(crate) async fn connect( None => None, }; - let connection = app_handle.db().upsert_websocket_connection( - &WebsocketConnection { - workspace_id: request.workspace_id.clone(), - request_id: request_id.to_string(), - ..Default::default() - }, - &UpdateSource::from_window(&window), - )?; - let (receive_tx, mut receive_rx) = mpsc::channel::(128); let mut ws_manager = ws_manager.lock().await; - let (mut url, url_parameters) = apply_path_placeholders(&request.url, request.url_parameters); - if !url.starts_with("ws://") && !url.starts_with("wss://") { - url.insert_str(0, "ws://"); - } - - // Add URL parameters to URL - let mut url = match Url::parse(&url) { - Ok(url) => url, - Err(e) => { - return Ok(app_handle.db().upsert_websocket_connection( - &WebsocketConnection { - error: Some(format!("Failed to parse URL {}", e.to_string())), - state: WebsocketConnectionState::Closed, - ..connection - }, - &UpdateSource::from_window(&window), - )?); - } - }; - { let valid_query_pairs = url_parameters .into_iter()