mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-05-25 08:59:46 +02:00
JWT auth plugin and necessary updates
This commit is contained in:
@@ -369,14 +369,8 @@ pub async fn send_http_request<R: Runtime>(
|
||||
};
|
||||
|
||||
// Apply authentication
|
||||
|
||||
// Map legacy auth name values from before they were plugins
|
||||
let auth_plugin_name = match request.authentication_type.clone() {
|
||||
Some(s) if s == "basic" => Some("@yaakapp/auth-basic".to_string()),
|
||||
Some(s) if s == "bearer" => Some("@yaakapp/auth-bearer".to_string()),
|
||||
_ => request.authentication_type.to_owned(),
|
||||
};
|
||||
if let Some(plugin_name) = auth_plugin_name {
|
||||
|
||||
if let Some(auth_name) = request.authentication_type.to_owned() {
|
||||
let req = CallHttpAuthenticationRequest {
|
||||
config: serde_json::to_value(&request.authentication)
|
||||
.unwrap()
|
||||
@@ -395,13 +389,13 @@ pub async fn send_http_request<R: Runtime>(
|
||||
.collect(),
|
||||
};
|
||||
let plugin_result =
|
||||
match plugin_manager.call_http_authentication(window, &plugin_name, req).await {
|
||||
match plugin_manager.call_http_authentication(window, &auth_name, req).await {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
return Ok(response_err(&*response.lock().await, e.to_string(), window).await);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
{
|
||||
let url = sendable_req.url_mut();
|
||||
*url = Url::parse(&plugin_result.url).unwrap();
|
||||
|
||||
+7
-14
@@ -235,19 +235,10 @@ async fn cmd_grpc_go<R: Runtime>(
|
||||
metadata.insert(h.name, h.value);
|
||||
}
|
||||
|
||||
// Map legacy auth name values from before they were plugins
|
||||
let auth_plugin_name = match req.authentication_type.clone() {
|
||||
Some(s) if s == "basic" => Some("@yaakapp/auth-basic".to_string()),
|
||||
Some(s) if s == "bearer" => Some("@yaakapp/auth-bearer".to_string()),
|
||||
_ => req.authentication_type.to_owned(),
|
||||
};
|
||||
if let Some(plugin_name) = auth_plugin_name {
|
||||
if let Some(auth_name) = req.authentication_type.clone() {
|
||||
let auth = req.authentication.clone();
|
||||
let plugin_req = CallHttpAuthenticationRequest {
|
||||
config: serde_json::to_value(&req.authentication)
|
||||
.unwrap()
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.to_owned(),
|
||||
config: serde_json::to_value(&auth).unwrap().as_object().unwrap().to_owned(),
|
||||
method: "POST".to_string(),
|
||||
url: req.url.clone(),
|
||||
headers: metadata
|
||||
@@ -259,7 +250,7 @@ async fn cmd_grpc_go<R: Runtime>(
|
||||
.collect(),
|
||||
};
|
||||
let plugin_result = plugin_manager
|
||||
.call_http_authentication(&window, &plugin_name, plugin_req)
|
||||
.call_http_authentication(&window, &auth_name, plugin_req)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
@@ -980,7 +971,9 @@ async fn cmd_get_http_authentication<R: Runtime>(
|
||||
window: WebviewWindow<R>,
|
||||
plugin_manager: State<'_, PluginManager>,
|
||||
) -> Result<Vec<GetHttpAuthenticationResponse>, String> {
|
||||
plugin_manager.get_http_authentication(&window).await.map_err(|e| e.to_string())
|
||||
let results =
|
||||
plugin_manager.get_http_authentication(&window).await.map_err(|e| e.to_string())?;
|
||||
Ok(results.into_iter().map(|(_, a)| a).collect())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
@@ -46,9 +46,10 @@ impl TemplateCallback for PluginTemplateCallback {
|
||||
let mut args_with_defaults = args.clone();
|
||||
|
||||
// Fill in default values for all args
|
||||
for a_def in function.args {
|
||||
let base = match a_def {
|
||||
for arg in function.args {
|
||||
let base = match arg {
|
||||
FormInput::Text(a) => a.base,
|
||||
FormInput::Editor(a) => a.base,
|
||||
FormInput::Select(a) => a.base,
|
||||
FormInput::Checkbox(a) => a.base,
|
||||
FormInput::File(a) => a.base,
|
||||
|
||||
+5
-2
@@ -25,7 +25,9 @@ __export(src_exports, {
|
||||
module.exports = __toCommonJS(src_exports);
|
||||
var plugin = {
|
||||
authentication: {
|
||||
name: "Basic",
|
||||
name: "basic",
|
||||
label: "Basic Auth",
|
||||
shortLabel: "Basic",
|
||||
config: [{
|
||||
type: "text",
|
||||
name: "username",
|
||||
@@ -35,7 +37,8 @@ var plugin = {
|
||||
type: "text",
|
||||
name: "password",
|
||||
label: "Password",
|
||||
optional: true
|
||||
optional: true,
|
||||
password: true
|
||||
}],
|
||||
async onApply(_ctx, args) {
|
||||
const { username, password } = args.config;
|
||||
|
||||
+5
-2
@@ -25,12 +25,15 @@ __export(src_exports, {
|
||||
module.exports = __toCommonJS(src_exports);
|
||||
var plugin = {
|
||||
authentication: {
|
||||
name: "Bearer",
|
||||
name: "bearer",
|
||||
label: "Bearer Token",
|
||||
shortLabel: "Bearer",
|
||||
config: [{
|
||||
type: "text",
|
||||
name: "token",
|
||||
label: "Token",
|
||||
optional: true
|
||||
optional: true,
|
||||
password: true
|
||||
}],
|
||||
async onApply(_ctx, args) {
|
||||
const { token } = args.config;
|
||||
|
||||
+48
-8
@@ -3793,17 +3793,57 @@ __export(src_exports, {
|
||||
});
|
||||
module.exports = __toCommonJS(src_exports);
|
||||
var import_jsonwebtoken = __toESM(require_jsonwebtoken());
|
||||
var algorithms = [
|
||||
"HS256",
|
||||
"HS384",
|
||||
"HS512",
|
||||
"RS256",
|
||||
"RS384",
|
||||
"RS512",
|
||||
"PS256",
|
||||
"PS384",
|
||||
"PS512",
|
||||
"ES256",
|
||||
"ES384",
|
||||
"ES512"
|
||||
];
|
||||
var defaultAlgorithm = algorithms[0];
|
||||
var plugin = {
|
||||
authentication: {
|
||||
name: "JWT",
|
||||
config: [{
|
||||
type: "text",
|
||||
name: "foo",
|
||||
label: "Foo",
|
||||
optional: true
|
||||
}],
|
||||
name: "jwt",
|
||||
label: "JWT Bearer",
|
||||
shortLabel: "JWT",
|
||||
config: [
|
||||
{
|
||||
type: "select",
|
||||
name: "algorithm",
|
||||
label: "Algorithm",
|
||||
defaultValue: defaultAlgorithm,
|
||||
options: algorithms.map((value) => ({ name: value, value }))
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
name: "secret",
|
||||
label: "Secret",
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
type: "checkbox",
|
||||
name: "secretBase64",
|
||||
label: "Secret Base64 Encoded"
|
||||
},
|
||||
{
|
||||
type: "editor",
|
||||
name: "payload",
|
||||
label: "Payload",
|
||||
language: "json",
|
||||
optional: true
|
||||
}
|
||||
],
|
||||
async onApply(_ctx, args) {
|
||||
const token = import_jsonwebtoken.default.sign({ foo: "bar" }, null);
|
||||
const { algorithm, secret: _secret, secretBase64, payload } = args.config;
|
||||
const secret = secretBase64 ? Buffer.from(`${_secret}`, "base64") : `${_secret}`;
|
||||
const token = import_jsonwebtoken.default.sign(`${payload}`, secret, { algorithm });
|
||||
return {
|
||||
url: args.url,
|
||||
headers: [{ name: "Authorization", value: `Bearer ${token}` }]
|
||||
|
||||
@@ -13,7 +13,7 @@ tokio-stream = "0.1.14"
|
||||
prost-types = "0.13.4"
|
||||
serde = { version = "1.0.196", features = ["derive"] }
|
||||
serde_json = "1.0.113"
|
||||
prost-reflect = { version = "0.14.4", features = ["serde", "derive"] }
|
||||
prost-reflect = { version = "0.14.4", default-features = false, features = ["serde", "derive"] }
|
||||
log = "0.4.20"
|
||||
anyhow = "1.0.79"
|
||||
hyper = "1.5.2"
|
||||
|
||||
@@ -29,6 +29,8 @@ export type Color = "custom" | "default" | "primary" | "secondary" | "info" | "s
|
||||
|
||||
export type CopyTextRequest = { text: string, };
|
||||
|
||||
export type EditorLanguage = "text" | "javascript" | "json" | "html" | "xml" | "graphql" | "markdown";
|
||||
|
||||
export type EmptyPayload = {};
|
||||
|
||||
export type ExportHttpRequestRequest = { httpRequest: HttpRequest, };
|
||||
@@ -49,7 +51,7 @@ export type FindHttpResponsesRequest = { requestId: string, limit?: number, };
|
||||
|
||||
export type FindHttpResponsesResponse = { httpResponses: Array<HttpResponse>, };
|
||||
|
||||
export type FormInput = { "type": "text" } & FormInputText | { "type": "select" } & FormInputSelect | { "type": "checkbox" } & FormInputCheckbox | { "type": "file" } & FormInputFile | { "type": "http_request" } & FormInputHttpRequest;
|
||||
export type FormInput = { "type": "text" } & FormInputText | { "type": "editor" } & FormInputEditor | { "type": "select" } & FormInputSelect | { "type": "checkbox" } & FormInputCheckbox | { "type": "file" } & FormInputFile | { "type": "http_request" } & FormInputHttpRequest;
|
||||
|
||||
export type FormInputBase = { name: string,
|
||||
/**
|
||||
@@ -79,6 +81,24 @@ label?: string,
|
||||
*/
|
||||
defaultValue?: string, };
|
||||
|
||||
export type FormInputEditor = {
|
||||
/**
|
||||
* Placeholder for the text input
|
||||
*/
|
||||
placeholder?: string | null, language: EditorLanguage, name: string,
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean,
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string,
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string, };
|
||||
|
||||
export type FormInputFile = {
|
||||
/**
|
||||
* The title of the file selection window
|
||||
@@ -139,7 +159,11 @@ export type FormInputText = {
|
||||
/**
|
||||
* Placeholder for the text input
|
||||
*/
|
||||
placeholder?: string | null, name: string,
|
||||
placeholder?: string | null,
|
||||
/**
|
||||
* Placeholder for the text input
|
||||
*/
|
||||
password?: boolean, name: string,
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
@@ -153,7 +177,7 @@ label?: string,
|
||||
*/
|
||||
defaultValue?: string, };
|
||||
|
||||
export type GetHttpAuthenticationResponse = { name: string, pluginName: string, config: Array<FormInput>, };
|
||||
export type GetHttpAuthenticationResponse = { name: string, label: string, shortLabel: string, config: Array<FormInput>, };
|
||||
|
||||
export type GetHttpRequestActionsRequest = Record<string, never>;
|
||||
|
||||
|
||||
@@ -25,6 +25,9 @@ pub enum Error {
|
||||
|
||||
#[error("Plugin not found: {0}")]
|
||||
PluginNotFoundErr(String),
|
||||
|
||||
#[error("Auth plugin not found: {0}")]
|
||||
AuthPluginNotFound(String),
|
||||
|
||||
#[error("Plugin error: {0}")]
|
||||
PluginErr(String),
|
||||
|
||||
@@ -298,7 +298,8 @@ pub enum Icon {
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
pub struct GetHttpAuthenticationResponse {
|
||||
pub name: String,
|
||||
pub plugin_name: String,
|
||||
pub label: String,
|
||||
pub short_label: String,
|
||||
pub config: Vec<FormInput>,
|
||||
}
|
||||
|
||||
@@ -356,6 +357,7 @@ pub struct TemplateFunction {
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
pub enum FormInput {
|
||||
Text(FormInputText),
|
||||
Editor(FormInputEditor),
|
||||
Select(FormInputSelect),
|
||||
Checkbox(FormInputCheckbox),
|
||||
File(FormInputFile),
|
||||
@@ -391,6 +393,43 @@ pub struct FormInputText {
|
||||
/// Placeholder for the text input
|
||||
#[ts(optional = nullable)]
|
||||
pub placeholder: Option<String>,
|
||||
|
||||
/// Placeholder for the text input
|
||||
#[ts(optional)]
|
||||
pub password: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
pub enum EditorLanguage {
|
||||
Text,
|
||||
Javascript,
|
||||
Json,
|
||||
Html,
|
||||
Xml,
|
||||
Graphql,
|
||||
Markdown,
|
||||
}
|
||||
|
||||
impl Default for EditorLanguage {
|
||||
fn default() -> Self {
|
||||
Self::Text
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
pub struct FormInputEditor {
|
||||
#[serde(flatten)]
|
||||
pub base: FormInputBase,
|
||||
|
||||
/// Placeholder for the text input
|
||||
#[ts(optional = nullable)]
|
||||
pub placeholder: Option<String>,
|
||||
|
||||
pub language: EditorLanguage,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use crate::error::Error::{ClientNotInitializedErr, PluginErr, PluginNotFoundErr, UnknownEventErr};
|
||||
use crate::error::Error::{
|
||||
AuthPluginNotFound, ClientNotInitializedErr, PluginErr, PluginNotFoundErr, UnknownEventErr,
|
||||
};
|
||||
use crate::error::Result;
|
||||
use crate::events::{
|
||||
BootRequest, CallHttpAuthenticationRequest, CallHttpAuthenticationResponse,
|
||||
CallHttpRequestActionRequest, CallTemplateFunctionArgs, CallTemplateFunctionRequest,
|
||||
CallTemplateFunctionResponse, EmptyPayload, FilterRequest, FilterResponse,
|
||||
CallTemplateFunctionResponse, EmptyPayload, FilterRequest, FilterResponse, FormInput,
|
||||
GetHttpAuthenticationResponse, GetHttpRequestActionsResponse, GetTemplateFunctionsResponse,
|
||||
ImportRequest, ImportResponse, InternalEvent, InternalEventPayload, RenderPurpose,
|
||||
WindowContext,
|
||||
@@ -462,7 +464,7 @@ impl PluginManager {
|
||||
pub async fn get_http_authentication<R: Runtime>(
|
||||
&self,
|
||||
window: &WebviewWindow<R>,
|
||||
) -> Result<Vec<GetHttpAuthenticationResponse>> {
|
||||
) -> Result<Vec<(PluginHandle, GetHttpAuthenticationResponse)>> {
|
||||
let window_context = WindowContext::from_window(window);
|
||||
let reply_events = self
|
||||
.send_and_wait(
|
||||
@@ -474,7 +476,11 @@ impl PluginManager {
|
||||
let mut results = Vec::new();
|
||||
for event in reply_events {
|
||||
if let InternalEventPayload::GetHttpAuthenticationResponse(resp) = event.payload {
|
||||
results.push(resp.clone());
|
||||
let plugin = self
|
||||
.get_plugin_by_ref_id(&event.plugin_ref_id)
|
||||
.await
|
||||
.ok_or(PluginNotFoundErr(event.plugin_ref_id))?;
|
||||
results.push((plugin, resp.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,13 +490,37 @@ impl PluginManager {
|
||||
pub async fn call_http_authentication<R: Runtime>(
|
||||
&self,
|
||||
window: &WebviewWindow<R>,
|
||||
plugin_name: &str,
|
||||
auth_name: &str,
|
||||
req: CallHttpAuthenticationRequest,
|
||||
) -> Result<CallHttpAuthenticationResponse> {
|
||||
let plugin = self
|
||||
.get_plugin_by_name(plugin_name)
|
||||
.await
|
||||
.ok_or(PluginNotFoundErr(plugin_name.to_string()))?;
|
||||
let handlers = self.get_http_authentication(window).await?;
|
||||
let (plugin, authentication) = handlers
|
||||
.iter()
|
||||
.find(|(_, a)| a.name == auth_name)
|
||||
.ok_or(AuthPluginNotFound(auth_name.to_string()))?;
|
||||
|
||||
// Clone for mutability
|
||||
let mut req = req.clone();
|
||||
|
||||
// Fill in default values
|
||||
for arg in authentication.config.clone() {
|
||||
let base = match arg {
|
||||
FormInput::Text(a) => a.base,
|
||||
FormInput::Editor(a) => a.base,
|
||||
FormInput::Select(a) => a.base,
|
||||
FormInput::Checkbox(a) => a.base,
|
||||
FormInput::File(a) => a.base,
|
||||
FormInput::HttpRequest(a) => a.base,
|
||||
};
|
||||
if let None = req.config.get(base.name.as_str()) {
|
||||
let default = match base.default_value {
|
||||
None => serde_json::Value::Null,
|
||||
Some(s) => serde_json::Value::String(s),
|
||||
};
|
||||
req.config.insert(base.name, default);
|
||||
}
|
||||
}
|
||||
|
||||
let event = self
|
||||
.send_to_plugin_and_wait(
|
||||
WindowContext::from_window(window),
|
||||
|
||||
Reference in New Issue
Block a user