mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-22 16:48:30 +02:00
Scoped OAuth 2 tokens
This commit is contained in:
@@ -1,19 +0,0 @@
|
|||||||
import type { Context } from '@yaakapp/api';
|
|
||||||
import type { AccessToken } from './store';
|
|
||||||
import { getToken } from './store';
|
|
||||||
|
|
||||||
export async function getAccessTokenIfNotExpired(
|
|
||||||
ctx: Context,
|
|
||||||
contextId: string,
|
|
||||||
): Promise<AccessToken | null> {
|
|
||||||
const token = await getToken(ctx, contextId);
|
|
||||||
if (token == null || isTokenExpired(token)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isTokenExpired(token: AccessToken) {
|
|
||||||
return token.expiresAt && Date.now() > token.expiresAt;
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import type { Context, HttpRequest } from '@yaakapp/api';
|
import type { Context, HttpRequest } from '@yaakapp/api';
|
||||||
import { readFileSync } from 'node:fs';
|
import { readFileSync } from 'node:fs';
|
||||||
import { isTokenExpired } from './getAccessTokenIfNotExpired';
|
import type { AccessToken, AccessTokenRawResponse, TokenStoreArgs } from './store';
|
||||||
import type { AccessToken, AccessTokenRawResponse } from './store';
|
|
||||||
import { deleteToken, getToken, storeToken } from './store';
|
import { deleteToken, getToken, storeToken } from './store';
|
||||||
|
import { isTokenExpired } from './util';
|
||||||
|
|
||||||
export async function getOrRefreshAccessToken(
|
export async function getOrRefreshAccessToken(
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
contextId: string,
|
tokenArgs: TokenStoreArgs,
|
||||||
{
|
{
|
||||||
scope,
|
scope,
|
||||||
accessTokenUrl,
|
accessTokenUrl,
|
||||||
@@ -23,7 +23,7 @@ export async function getOrRefreshAccessToken(
|
|||||||
forceRefresh?: boolean;
|
forceRefresh?: boolean;
|
||||||
},
|
},
|
||||||
): Promise<AccessToken | null> {
|
): Promise<AccessToken | null> {
|
||||||
const token = await getToken(ctx, contextId);
|
const token = await getToken(ctx, tokenArgs);
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,7 @@ export async function getOrRefreshAccessToken(
|
|||||||
// Bad refresh token, so we'll force it to fetch a fresh access token by deleting
|
// Bad refresh token, so we'll force it to fetch a fresh access token by deleting
|
||||||
// and returning null;
|
// and returning null;
|
||||||
console.log('[oauth2] Unauthorized refresh_token request');
|
console.log('[oauth2] Unauthorized refresh_token request');
|
||||||
await deleteToken(ctx, contextId);
|
await deleteToken(ctx, tokenArgs);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,5 +108,5 @@ export async function getOrRefreshAccessToken(
|
|||||||
refresh_token: response.refresh_token ?? token.response.refresh_token,
|
refresh_token: response.refresh_token ?? token.response.refresh_token,
|
||||||
};
|
};
|
||||||
|
|
||||||
return storeToken(ctx, contextId, newResponse);
|
return storeToken(ctx, tokenArgs, newResponse);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Context } from '@yaakapp/api';
|
|||||||
import { createHash, randomBytes } from 'node:crypto';
|
import { createHash, randomBytes } from 'node:crypto';
|
||||||
import { fetchAccessToken } from '../fetchAccessToken';
|
import { fetchAccessToken } from '../fetchAccessToken';
|
||||||
import { getOrRefreshAccessToken } from '../getOrRefreshAccessToken';
|
import { getOrRefreshAccessToken } from '../getOrRefreshAccessToken';
|
||||||
import type { AccessToken } from '../store';
|
import type { AccessToken, TokenStoreArgs } from '../store';
|
||||||
import { getDataDirKey, storeToken } from '../store';
|
import { getDataDirKey, storeToken } from '../store';
|
||||||
|
|
||||||
export const PKCE_SHA256 = 'S256';
|
export const PKCE_SHA256 = 'S256';
|
||||||
@@ -41,7 +41,14 @@ export async function getAuthorizationCode(
|
|||||||
tokenName: 'access_token' | 'id_token';
|
tokenName: 'access_token' | 'id_token';
|
||||||
},
|
},
|
||||||
): Promise<AccessToken> {
|
): Promise<AccessToken> {
|
||||||
const token = await getOrRefreshAccessToken(ctx, contextId, {
|
const tokenArgs: TokenStoreArgs = {
|
||||||
|
contextId,
|
||||||
|
clientId,
|
||||||
|
accessTokenUrl,
|
||||||
|
authorizationUrl: authorizationUrlRaw,
|
||||||
|
};
|
||||||
|
|
||||||
|
const token = await getOrRefreshAccessToken(ctx, tokenArgs, {
|
||||||
accessTokenUrl,
|
accessTokenUrl,
|
||||||
scope,
|
scope,
|
||||||
clientId,
|
clientId,
|
||||||
@@ -128,7 +135,7 @@ export async function getAuthorizationCode(
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
return storeToken(ctx, contextId, response, tokenName);
|
return storeToken(ctx, tokenArgs, response, tokenName);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function genPkceCodeVerifier() {
|
export function genPkceCodeVerifier() {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import type { Context } from '@yaakapp/api';
|
import type { Context } from '@yaakapp/api';
|
||||||
import { fetchAccessToken } from '../fetchAccessToken';
|
import { fetchAccessToken } from '../fetchAccessToken';
|
||||||
import { isTokenExpired } from '../getAccessTokenIfNotExpired';
|
import type { TokenStoreArgs } from '../store';
|
||||||
import { getToken, storeToken } from '../store';
|
import { getToken, storeToken } from '../store';
|
||||||
|
import { isTokenExpired } from '../util';
|
||||||
|
|
||||||
export async function getClientCredentials(
|
export async function getClientCredentials(
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
@@ -22,7 +23,13 @@ export async function getClientCredentials(
|
|||||||
credentialsInBody: boolean;
|
credentialsInBody: boolean;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const token = await getToken(ctx, contextId);
|
const tokenArgs: TokenStoreArgs = {
|
||||||
|
contextId,
|
||||||
|
clientId,
|
||||||
|
accessTokenUrl,
|
||||||
|
authorizationUrl: null,
|
||||||
|
};
|
||||||
|
const token = await getToken(ctx, tokenArgs);
|
||||||
if (token && !isTokenExpired(token)) {
|
if (token && !isTokenExpired(token)) {
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
@@ -38,5 +45,5 @@ export async function getClientCredentials(
|
|||||||
params: [],
|
params: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
return storeToken(ctx, contextId, response);
|
return storeToken(ctx, tokenArgs, response);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { Context } from '@yaakapp/api';
|
import type { Context } from '@yaakapp/api';
|
||||||
import { isTokenExpired } from '../getAccessTokenIfNotExpired';
|
import type { AccessToken, AccessTokenRawResponse } from '../store';
|
||||||
import type { AccessToken, AccessTokenRawResponse} from '../store';
|
|
||||||
import { getToken, storeToken } from '../store';
|
import { getToken, storeToken } from '../store';
|
||||||
|
import { isTokenExpired } from '../util';
|
||||||
|
|
||||||
export async function getImplicit(
|
export async function getImplicit(
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
@@ -26,7 +26,13 @@ export async function getImplicit(
|
|||||||
tokenName: 'access_token' | 'id_token';
|
tokenName: 'access_token' | 'id_token';
|
||||||
},
|
},
|
||||||
): Promise<AccessToken> {
|
): Promise<AccessToken> {
|
||||||
const token = await getToken(ctx, contextId);
|
const tokenArgs = {
|
||||||
|
contextId,
|
||||||
|
clientId,
|
||||||
|
accessTokenUrl: null,
|
||||||
|
authorizationUrl: authorizationUrlRaw,
|
||||||
|
};
|
||||||
|
const token = await getToken(ctx, tokenArgs);
|
||||||
if (token != null && !isTokenExpired(token)) {
|
if (token != null && !isTokenExpired(token)) {
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
@@ -82,7 +88,7 @@ export async function getImplicit(
|
|||||||
|
|
||||||
const response = Object.fromEntries(params) as unknown as AccessTokenRawResponse;
|
const response = Object.fromEntries(params) as unknown as AccessTokenRawResponse;
|
||||||
try {
|
try {
|
||||||
resolve(storeToken(ctx, contextId, response));
|
resolve(storeToken(ctx, tokenArgs, response));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { Context } from '@yaakapp/api';
|
import type { Context } from '@yaakapp/api';
|
||||||
import { fetchAccessToken } from '../fetchAccessToken';
|
import { fetchAccessToken } from '../fetchAccessToken';
|
||||||
import { getOrRefreshAccessToken } from '../getOrRefreshAccessToken';
|
import { getOrRefreshAccessToken } from '../getOrRefreshAccessToken';
|
||||||
import type { AccessToken} from '../store';
|
import type { AccessToken, TokenStoreArgs } from '../store';
|
||||||
import { storeToken } from '../store';
|
import { storeToken } from '../store';
|
||||||
|
|
||||||
export async function getPassword(
|
export async function getPassword(
|
||||||
@@ -27,7 +27,13 @@ export async function getPassword(
|
|||||||
credentialsInBody: boolean;
|
credentialsInBody: boolean;
|
||||||
},
|
},
|
||||||
): Promise<AccessToken> {
|
): Promise<AccessToken> {
|
||||||
const token = await getOrRefreshAccessToken(ctx, contextId, {
|
const tokenArgs: TokenStoreArgs = {
|
||||||
|
contextId,
|
||||||
|
clientId,
|
||||||
|
accessTokenUrl,
|
||||||
|
authorizationUrl: null,
|
||||||
|
};
|
||||||
|
const token = await getOrRefreshAccessToken(ctx, tokenArgs, {
|
||||||
accessTokenUrl,
|
accessTokenUrl,
|
||||||
scope,
|
scope,
|
||||||
clientId,
|
clientId,
|
||||||
@@ -52,5 +58,5 @@ export async function getPassword(
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
return storeToken(ctx, contextId, response);
|
return storeToken(ctx, tokenArgs, response);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
import { getClientCredentials } from './grants/clientCredentials';
|
import { getClientCredentials } from './grants/clientCredentials';
|
||||||
import { getImplicit } from './grants/implicit';
|
import { getImplicit } from './grants/implicit';
|
||||||
import { getPassword } from './grants/password';
|
import { getPassword } from './grants/password';
|
||||||
import type { AccessToken } from './store';
|
import type { AccessToken, TokenStoreArgs } from './store';
|
||||||
import { deleteToken, getToken, resetDataDirKey } from './store';
|
import { deleteToken, getToken, resetDataDirKey } from './store';
|
||||||
|
|
||||||
type GrantType = 'authorization_code' | 'implicit' | 'password' | 'client_credentials';
|
type GrantType = 'authorization_code' | 'implicit' | 'password' | 'client_credentials';
|
||||||
@@ -83,8 +83,14 @@ export const plugin: PluginDefinition = {
|
|||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
label: 'Copy Current Token',
|
label: 'Copy Current Token',
|
||||||
async onSelect(ctx, { contextId }) {
|
async onSelect(ctx, { contextId, values }) {
|
||||||
const token = await getToken(ctx, contextId);
|
const tokenArgs: TokenStoreArgs = {
|
||||||
|
contextId,
|
||||||
|
authorizationUrl: stringArg(values, 'authorizationUrl'),
|
||||||
|
accessTokenUrl: stringArg(values, 'accessTokenUrl'),
|
||||||
|
clientId: stringArg(values, 'clientId'),
|
||||||
|
};
|
||||||
|
const token = await getToken(ctx, tokenArgs);
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
await ctx.toast.show({ message: 'No token to copy', color: 'warning' });
|
await ctx.toast.show({ message: 'No token to copy', color: 'warning' });
|
||||||
} else {
|
} else {
|
||||||
@@ -99,8 +105,14 @@ export const plugin: PluginDefinition = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Delete Token',
|
label: 'Delete Token',
|
||||||
async onSelect(ctx, { contextId }) {
|
async onSelect(ctx, { contextId, values }) {
|
||||||
if (await deleteToken(ctx, contextId)) {
|
const tokenArgs: TokenStoreArgs = {
|
||||||
|
contextId,
|
||||||
|
authorizationUrl: stringArg(values, 'authorizationUrl'),
|
||||||
|
accessTokenUrl: stringArg(values, 'accessTokenUrl'),
|
||||||
|
clientId: stringArg(values, 'clientId'),
|
||||||
|
};
|
||||||
|
if (await deleteToken(ctx, tokenArgs)) {
|
||||||
await ctx.toast.show({ message: 'Token deleted', color: 'success' });
|
await ctx.toast.show({ message: 'Token deleted', color: 'success' });
|
||||||
} else {
|
} else {
|
||||||
await ctx.toast.show({ message: 'No token to delete', color: 'warning' });
|
await ctx.toast.show({ message: 'No token to delete', color: 'warning' });
|
||||||
@@ -281,8 +293,14 @@ export const plugin: PluginDefinition = {
|
|||||||
{
|
{
|
||||||
type: 'accordion',
|
type: 'accordion',
|
||||||
label: 'Access Token Response',
|
label: 'Access Token Response',
|
||||||
async dynamic(ctx, { contextId }) {
|
async dynamic(ctx, { contextId, values }) {
|
||||||
const token = await getToken(ctx, contextId);
|
const tokenArgs: TokenStoreArgs = {
|
||||||
|
contextId,
|
||||||
|
authorizationUrl: stringArg(values, 'authorizationUrl'),
|
||||||
|
accessTokenUrl: stringArg(values, 'accessTokenUrl'),
|
||||||
|
clientId: stringArg(values, 'clientId'),
|
||||||
|
};
|
||||||
|
const token = await getToken(ctx, tokenArgs);
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
return { hidden: true };
|
return { hidden: true };
|
||||||
}
|
}
|
||||||
@@ -316,9 +334,10 @@ export const plugin: PluginDefinition = {
|
|||||||
accessTokenUrl === '' || accessTokenUrl.match(/^https?:\/\//)
|
accessTokenUrl === '' || accessTokenUrl.match(/^https?:\/\//)
|
||||||
? accessTokenUrl
|
? accessTokenUrl
|
||||||
: `https://${accessTokenUrl}`,
|
: `https://${accessTokenUrl}`,
|
||||||
authorizationUrl: authorizationUrl === '' || authorizationUrl.match(/^https?:\/\//)
|
authorizationUrl:
|
||||||
? authorizationUrl
|
authorizationUrl === '' || authorizationUrl.match(/^https?:\/\//)
|
||||||
: `https://${authorizationUrl}`,
|
? authorizationUrl
|
||||||
|
: `https://${authorizationUrl}`,
|
||||||
clientId: stringArg(values, 'clientId'),
|
clientId: stringArg(values, 'clientId'),
|
||||||
clientSecret: stringArg(values, 'clientSecret'),
|
clientSecret: stringArg(values, 'clientSecret'),
|
||||||
redirectUri: stringArgOrNull(values, 'redirectUri'),
|
redirectUri: stringArgOrNull(values, 'redirectUri'),
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import type { Context } from '@yaakapp/api';
|
import type { Context } from '@yaakapp/api';
|
||||||
|
import { createHash } from 'node:crypto';
|
||||||
|
|
||||||
export async function storeToken(
|
export async function storeToken(
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
contextId: string,
|
args: TokenStoreArgs,
|
||||||
response: AccessTokenRawResponse,
|
response: AccessTokenRawResponse,
|
||||||
tokenName: 'access_token' | 'id_token' = 'access_token',
|
tokenName: 'access_token' | 'id_token' = 'access_token',
|
||||||
) {
|
) {
|
||||||
@@ -15,16 +16,16 @@ export async function storeToken(
|
|||||||
response,
|
response,
|
||||||
expiresAt,
|
expiresAt,
|
||||||
};
|
};
|
||||||
await ctx.store.set<AccessToken>(tokenStoreKey(contextId), token);
|
await ctx.store.set<AccessToken>(tokenStoreKey(args), token);
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getToken(ctx: Context, contextId: string) {
|
export async function getToken(ctx: Context, args: TokenStoreArgs) {
|
||||||
return ctx.store.get<AccessToken>(tokenStoreKey(contextId));
|
return ctx.store.get<AccessToken>(tokenStoreKey(args));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteToken(ctx: Context, contextId: string) {
|
export async function deleteToken(ctx: Context, args: TokenStoreArgs) {
|
||||||
return ctx.store.delete(tokenStoreKey(contextId));
|
return ctx.store.delete(tokenStoreKey(args));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function resetDataDirKey(ctx: Context, contextId: string) {
|
export async function resetDataDirKey(ctx: Context, contextId: string) {
|
||||||
@@ -37,8 +38,25 @@ export async function getDataDirKey(ctx: Context, contextId: string) {
|
|||||||
return `${contextId}::${key}`;
|
return `${contextId}::${key}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function tokenStoreKey(contextId: string) {
|
export interface TokenStoreArgs {
|
||||||
return ['token', contextId].join('::');
|
contextId: string;
|
||||||
|
clientId: string;
|
||||||
|
accessTokenUrl: string | null;
|
||||||
|
authorizationUrl: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a store key to use based on some arguments. The arguments will be normalized a bit to
|
||||||
|
* account for slight variations (like domains with and without a protocol scheme).
|
||||||
|
*/
|
||||||
|
function tokenStoreKey(args: TokenStoreArgs) {
|
||||||
|
const hash = createHash('md5');
|
||||||
|
if (args.contextId) hash.update(args.contextId.trim());
|
||||||
|
if (args.clientId) hash.update(args.clientId.trim());
|
||||||
|
if (args.accessTokenUrl) hash.update(args.accessTokenUrl.trim().replace(/^https?:\/\//, ''));
|
||||||
|
if (args.authorizationUrl) hash.update(args.authorizationUrl.trim().replace(/^https?:\/\//, ''));
|
||||||
|
const key = hash.digest('hex');
|
||||||
|
return ['token', key].join('::');
|
||||||
}
|
}
|
||||||
|
|
||||||
function dataDirStoreKey(contextId: string) {
|
function dataDirStoreKey(contextId: string) {
|
||||||
|
|||||||
5
plugins/auth-oauth2/src/util.ts
Normal file
5
plugins/auth-oauth2/src/util.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import type { AccessToken } from './store';
|
||||||
|
|
||||||
|
export function isTokenExpired(token: AccessToken) {
|
||||||
|
return token.expiresAt && Date.now() > token.expiresAt;
|
||||||
|
}
|
||||||
@@ -55,11 +55,6 @@ pub async fn send_http_request<R: Runtime>(
|
|||||||
let response_id = og_response.id.clone();
|
let response_id = og_response.id.clone();
|
||||||
let response = Arc::new(Mutex::new(og_response.clone()));
|
let response = Arc::new(Mutex::new(og_response.clone()));
|
||||||
|
|
||||||
let cb = PluginTemplateCallback::new(
|
|
||||||
window.app_handle(),
|
|
||||||
&PluginWindowContext::new(window),
|
|
||||||
RenderPurpose::Send,
|
|
||||||
);
|
|
||||||
let update_source = UpdateSource::from_window(window);
|
let update_source = UpdateSource::from_window(window);
|
||||||
|
|
||||||
let (resolved_request, auth_context_id) = match resolve_http_request(window, unrendered_request)
|
let (resolved_request, auth_context_id) = match resolve_http_request(window, unrendered_request)
|
||||||
@@ -75,6 +70,12 @@ pub async fn send_http_request<R: Runtime>(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let cb = PluginTemplateCallback::new(
|
||||||
|
window.app_handle(),
|
||||||
|
&PluginWindowContext::new(window),
|
||||||
|
RenderPurpose::Send,
|
||||||
|
);
|
||||||
|
|
||||||
let request =
|
let request =
|
||||||
match render_http_request(&resolved_request, &base_environment, environment.as_ref(), &cb)
|
match render_http_request(&resolved_request, &base_environment, environment.as_ref(), &cb)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -35,7 +35,13 @@ use yaak_models::models::{
|
|||||||
};
|
};
|
||||||
use yaak_models::query_manager::QueryManagerExt;
|
use yaak_models::query_manager::QueryManagerExt;
|
||||||
use yaak_models::util::{BatchUpsertResult, UpdateSource, get_workspace_export_resources};
|
use yaak_models::util::{BatchUpsertResult, UpdateSource, get_workspace_export_resources};
|
||||||
use yaak_plugins::events::{CallGrpcRequestActionArgs, CallGrpcRequestActionRequest, CallHttpRequestActionArgs, CallHttpRequestActionRequest, Color, FilterResponse, GetGrpcRequestActionsResponse, GetHttpAuthenticationConfigResponse, GetHttpAuthenticationSummaryResponse, GetHttpRequestActionsResponse, GetTemplateFunctionsResponse, InternalEvent, InternalEventPayload, JsonPrimitive, PluginWindowContext, RenderPurpose, ShowToastRequest};
|
use yaak_plugins::events::{
|
||||||
|
CallGrpcRequestActionArgs, CallGrpcRequestActionRequest, CallHttpRequestActionArgs,
|
||||||
|
CallHttpRequestActionRequest, Color, FilterResponse, GetGrpcRequestActionsResponse,
|
||||||
|
GetHttpAuthenticationConfigResponse, GetHttpAuthenticationSummaryResponse,
|
||||||
|
GetHttpRequestActionsResponse, GetTemplateFunctionsResponse, InternalEvent,
|
||||||
|
InternalEventPayload, JsonPrimitive, PluginWindowContext, RenderPurpose, ShowToastRequest,
|
||||||
|
};
|
||||||
use yaak_plugins::manager::PluginManager;
|
use yaak_plugins::manager::PluginManager;
|
||||||
use yaak_plugins::plugin_meta::PluginMetadata;
|
use yaak_plugins::plugin_meta::PluginMetadata;
|
||||||
use yaak_plugins::template_callback::PluginTemplateCallback;
|
use yaak_plugins::template_callback::PluginTemplateCallback;
|
||||||
@@ -818,9 +824,29 @@ async fn cmd_get_http_authentication_config<R: Runtime>(
|
|||||||
auth_name: &str,
|
auth_name: &str,
|
||||||
values: HashMap<String, JsonPrimitive>,
|
values: HashMap<String, JsonPrimitive>,
|
||||||
request_id: &str,
|
request_id: &str,
|
||||||
|
environment_id: Option<&str>,
|
||||||
|
workspace_id: &str,
|
||||||
) -> YaakResult<GetHttpAuthenticationConfigResponse> {
|
) -> YaakResult<GetHttpAuthenticationConfigResponse> {
|
||||||
|
let base_environment = window.db().get_base_environment(&workspace_id)?;
|
||||||
|
let environment = match environment_id {
|
||||||
|
Some(id) => match window.db().get_environment(id) {
|
||||||
|
Ok(env) => Some(env),
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Failed to find environment by id {id} {}", e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
Ok(plugin_manager
|
Ok(plugin_manager
|
||||||
.get_http_authentication_config(&window, auth_name, values, request_id)
|
.get_http_authentication_config(
|
||||||
|
&window,
|
||||||
|
&base_environment,
|
||||||
|
environment.as_ref(),
|
||||||
|
auth_name,
|
||||||
|
values,
|
||||||
|
request_id,
|
||||||
|
)
|
||||||
.await?)
|
.await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -872,9 +898,30 @@ async fn cmd_call_http_authentication_action<R: Runtime>(
|
|||||||
action_index: i32,
|
action_index: i32,
|
||||||
values: HashMap<String, JsonPrimitive>,
|
values: HashMap<String, JsonPrimitive>,
|
||||||
model_id: &str,
|
model_id: &str,
|
||||||
|
workspace_id: &str,
|
||||||
|
environment_id: Option<&str>,
|
||||||
) -> YaakResult<()> {
|
) -> YaakResult<()> {
|
||||||
|
let base_environment = window.db().get_base_environment(&workspace_id)?;
|
||||||
|
let environment = match environment_id {
|
||||||
|
Some(id) => match window.db().get_environment(id) {
|
||||||
|
Ok(env) => Some(env),
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Failed to find environment by id {id} {}", e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
Ok(plugin_manager
|
Ok(plugin_manager
|
||||||
.call_http_authentication_action(&window, auth_name, action_index, values, model_id)
|
.call_http_authentication_action(
|
||||||
|
&window,
|
||||||
|
&base_environment,
|
||||||
|
environment.as_ref(),
|
||||||
|
auth_name,
|
||||||
|
action_index,
|
||||||
|
values,
|
||||||
|
model_id,
|
||||||
|
)
|
||||||
.await?)
|
.await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1238,7 +1285,10 @@ pub fn run() {
|
|||||||
let _ = app_handle.emit(
|
let _ = app_handle.emit(
|
||||||
"show_toast",
|
"show_toast",
|
||||||
ShowToastRequest {
|
ShowToastRequest {
|
||||||
message: format!("Error handling deep link: {}", e.to_string()),
|
message: format!(
|
||||||
|
"Error handling deep link: {}",
|
||||||
|
e.to_string()
|
||||||
|
),
|
||||||
color: Some(Color::Danger),
|
color: Some(Color::Danger),
|
||||||
icon: None,
|
icon: None,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ use crate::native_template_functions::template_function_secure;
|
|||||||
use crate::nodejs::start_nodejs_plugin_runtime;
|
use crate::nodejs::start_nodejs_plugin_runtime;
|
||||||
use crate::plugin_handle::PluginHandle;
|
use crate::plugin_handle::PluginHandle;
|
||||||
use crate::server_ws::PluginRuntimeServerWebsocket;
|
use crate::server_ws::PluginRuntimeServerWebsocket;
|
||||||
|
use crate::template_callback::PluginTemplateCallback;
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
|
use serde_json::json;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@@ -30,10 +32,13 @@ use tokio::fs::read_dir;
|
|||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
use tokio::sync::{Mutex, mpsc};
|
use tokio::sync::{Mutex, mpsc};
|
||||||
use tokio::time::{Instant, timeout};
|
use tokio::time::{Instant, timeout};
|
||||||
|
use yaak_models::models::Environment;
|
||||||
use yaak_models::query_manager::QueryManagerExt;
|
use yaak_models::query_manager::QueryManagerExt;
|
||||||
|
use yaak_models::render::make_vars_hashmap;
|
||||||
use yaak_models::util::generate_id;
|
use yaak_models::util::generate_id;
|
||||||
use yaak_templates::error::Error::RenderError;
|
use yaak_templates::error::Error::RenderError;
|
||||||
use yaak_templates::error::Result as TemplateResult;
|
use yaak_templates::error::Result as TemplateResult;
|
||||||
|
use yaak_templates::render_json_value_raw;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct PluginManager {
|
pub struct PluginManager {
|
||||||
@@ -569,6 +574,8 @@ impl PluginManager {
|
|||||||
pub async fn get_http_authentication_config<R: Runtime>(
|
pub async fn get_http_authentication_config<R: Runtime>(
|
||||||
&self,
|
&self,
|
||||||
window: &WebviewWindow<R>,
|
window: &WebviewWindow<R>,
|
||||||
|
base_environment: &Environment,
|
||||||
|
environment: Option<&Environment>,
|
||||||
auth_name: &str,
|
auth_name: &str,
|
||||||
values: HashMap<String, JsonPrimitive>,
|
values: HashMap<String, JsonPrimitive>,
|
||||||
request_id: &str,
|
request_id: &str,
|
||||||
@@ -579,13 +586,23 @@ impl PluginManager {
|
|||||||
.find_map(|(p, r)| if r.name == auth_name { Some(p) } else { None })
|
.find_map(|(p, r)| if r.name == auth_name { Some(p) } else { None })
|
||||||
.ok_or(PluginNotFoundErr(auth_name.into()))?;
|
.ok_or(PluginNotFoundErr(auth_name.into()))?;
|
||||||
|
|
||||||
|
let vars = &make_vars_hashmap(&base_environment, environment);
|
||||||
|
let cb = PluginTemplateCallback::new(
|
||||||
|
window.app_handle(),
|
||||||
|
&PluginWindowContext::new(&window),
|
||||||
|
RenderPurpose::Preview,
|
||||||
|
);
|
||||||
|
let rendered_values = render_json_value_raw(json!(values), vars, &cb).await?;
|
||||||
let context_id = format!("{:x}", md5::compute(request_id.to_string()));
|
let context_id = format!("{:x}", md5::compute(request_id.to_string()));
|
||||||
let event = self
|
let event = self
|
||||||
.send_to_plugin_and_wait(
|
.send_to_plugin_and_wait(
|
||||||
&PluginWindowContext::new(window),
|
&PluginWindowContext::new(window),
|
||||||
&plugin,
|
&plugin,
|
||||||
&InternalEventPayload::GetHttpAuthenticationConfigRequest(
|
&InternalEventPayload::GetHttpAuthenticationConfigRequest(
|
||||||
GetHttpAuthenticationConfigRequest { values, context_id },
|
GetHttpAuthenticationConfigRequest {
|
||||||
|
values: serde_json::from_value(rendered_values)?,
|
||||||
|
context_id,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -602,11 +619,24 @@ impl PluginManager {
|
|||||||
pub async fn call_http_authentication_action<R: Runtime>(
|
pub async fn call_http_authentication_action<R: Runtime>(
|
||||||
&self,
|
&self,
|
||||||
window: &WebviewWindow<R>,
|
window: &WebviewWindow<R>,
|
||||||
|
base_environment: &Environment,
|
||||||
|
environment: Option<&Environment>,
|
||||||
auth_name: &str,
|
auth_name: &str,
|
||||||
action_index: i32,
|
action_index: i32,
|
||||||
values: HashMap<String, JsonPrimitive>,
|
values: HashMap<String, JsonPrimitive>,
|
||||||
model_id: &str,
|
model_id: &str,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
let vars = &make_vars_hashmap(&base_environment, environment);
|
||||||
|
let rendered_values = render_json_value_raw(
|
||||||
|
json!(values),
|
||||||
|
vars,
|
||||||
|
&PluginTemplateCallback::new(
|
||||||
|
window.app_handle(),
|
||||||
|
&PluginWindowContext::new(&window),
|
||||||
|
RenderPurpose::Preview,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
let results = self.get_http_authentication_summaries(window).await?;
|
let results = self.get_http_authentication_summaries(window).await?;
|
||||||
let plugin = results
|
let plugin = results
|
||||||
.iter()
|
.iter()
|
||||||
@@ -621,7 +651,10 @@ impl PluginManager {
|
|||||||
CallHttpAuthenticationActionRequest {
|
CallHttpAuthenticationActionRequest {
|
||||||
index: action_index,
|
index: action_index,
|
||||||
plugin_ref_id: plugin.clone().ref_id,
|
plugin_ref_id: plugin.clone().ref_id,
|
||||||
args: CallHttpAuthenticationActionArgs { context_id, values },
|
args: CallHttpAuthenticationActionArgs {
|
||||||
|
context_id,
|
||||||
|
values: serde_json::from_value(rendered_values)?,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -182,23 +182,24 @@ function FormInputs<T extends Record<string, JsonPrimitive>>({
|
|||||||
);
|
);
|
||||||
case 'accordion':
|
case 'accordion':
|
||||||
return (
|
return (
|
||||||
<DetailsBanner
|
<div key={i}>
|
||||||
key={i}
|
<DetailsBanner
|
||||||
summary={input.label}
|
summary={input.label}
|
||||||
className={classNames(disabled && 'opacity-disabled')}
|
className={classNames('!mb-auto', disabled && 'opacity-disabled')}
|
||||||
>
|
>
|
||||||
<div className="mb-3 px-3">
|
<div className="mb-3 px-3">
|
||||||
<FormInputs
|
<FormInputs
|
||||||
data={data}
|
data={data}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
inputs={input.inputs}
|
inputs={input.inputs}
|
||||||
setDataAttr={setDataAttr}
|
setDataAttr={setDataAttr}
|
||||||
stateKey={stateKey}
|
stateKey={stateKey}
|
||||||
autocompleteFunctions={autocompleteFunctions || false}
|
autocompleteFunctions={autocompleteFunctions || false}
|
||||||
autocompleteVariables={autocompleteVariables}
|
autocompleteVariables={autocompleteVariables}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</DetailsBanner>
|
</DetailsBanner>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
case 'banner':
|
case 'banner':
|
||||||
return (
|
return (
|
||||||
@@ -309,6 +310,7 @@ function EditorArg({
|
|||||||
autocomplete={arg.completionOptions ? { options: arg.completionOptions } : undefined}
|
autocomplete={arg.completionOptions ? { options: arg.completionOptions } : undefined}
|
||||||
disabled={arg.disabled}
|
disabled={arg.disabled}
|
||||||
language={arg.language}
|
language={arg.language}
|
||||||
|
readOnly={arg.readOnly}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
heightMode="auto"
|
heightMode="auto"
|
||||||
defaultValue={value === DYNAMIC_FORM_NULL_ARG ? arg.defaultValue : value}
|
defaultValue={value === DYNAMIC_FORM_NULL_ARG ? arg.defaultValue : value}
|
||||||
@@ -329,9 +331,9 @@ function EditorArg({
|
|||||||
showDialog({
|
showDialog({
|
||||||
id: 'id',
|
id: 'id',
|
||||||
size: 'full',
|
size: 'full',
|
||||||
title: 'Edit Value',
|
title: arg.readOnly ? 'View Value' : 'Edit Value',
|
||||||
className: '!max-w-[50rem] !max-h-[60rem]',
|
className: '!max-w-[50rem] !max-h-[60rem]',
|
||||||
description: (
|
description: arg.label && (
|
||||||
<Label
|
<Label
|
||||||
htmlFor={id}
|
htmlFor={id}
|
||||||
required={!arg.optional}
|
required={!arg.optional}
|
||||||
@@ -355,6 +357,7 @@ function EditorArg({
|
|||||||
}
|
}
|
||||||
disabled={arg.disabled}
|
disabled={arg.disabled}
|
||||||
language={arg.language}
|
language={arg.language}
|
||||||
|
readOnly={arg.readOnly}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
defaultValue={value === DYNAMIC_FORM_NULL_ARG ? arg.defaultValue : value}
|
defaultValue={value === DYNAMIC_FORM_NULL_ARG ? arg.defaultValue : value}
|
||||||
placeholder={arg.placeholder ?? undefined}
|
placeholder={arg.placeholder ?? undefined}
|
||||||
|
|||||||
@@ -12,12 +12,16 @@ import { useAtomValue } from 'jotai';
|
|||||||
import { md5 } from 'js-md5';
|
import { md5 } from 'js-md5';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { invokeCmd } from '../lib/tauri';
|
import { invokeCmd } from '../lib/tauri';
|
||||||
|
import { activeEnvironmentIdAtom } from './useActiveEnvironment';
|
||||||
|
import { activeWorkspaceIdAtom } from './useActiveWorkspace';
|
||||||
|
|
||||||
export function useHttpAuthenticationConfig(
|
export function useHttpAuthenticationConfig(
|
||||||
authName: string | null,
|
authName: string | null,
|
||||||
values: Record<string, JsonPrimitive>,
|
values: Record<string, JsonPrimitive>,
|
||||||
requestId: string,
|
requestId: string,
|
||||||
) {
|
) {
|
||||||
|
const workspaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||||
|
const environmentId = useAtomValue(activeEnvironmentIdAtom);
|
||||||
const responses = useAtomValue(httpResponsesAtom);
|
const responses = useAtomValue(httpResponsesAtom);
|
||||||
const [forceRefreshCounter, setForceRefreshCounter] = useState<number>(0);
|
const [forceRefreshCounter, setForceRefreshCounter] = useState<number>(0);
|
||||||
|
|
||||||
@@ -38,6 +42,8 @@ export function useHttpAuthenticationConfig(
|
|||||||
values,
|
values,
|
||||||
responseKey,
|
responseKey,
|
||||||
forceRefreshCounter,
|
forceRefreshCounter,
|
||||||
|
workspaceId,
|
||||||
|
environmentId,
|
||||||
],
|
],
|
||||||
placeholderData: (prev) => prev, // Keep previous data on refetch
|
placeholderData: (prev) => prev, // Keep previous data on refetch
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
@@ -48,6 +54,8 @@ export function useHttpAuthenticationConfig(
|
|||||||
authName,
|
authName,
|
||||||
values,
|
values,
|
||||||
requestId,
|
requestId,
|
||||||
|
workspaceId,
|
||||||
|
environmentId,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -64,6 +72,8 @@ export function useHttpAuthenticationConfig(
|
|||||||
authName,
|
authName,
|
||||||
values,
|
values,
|
||||||
modelId,
|
modelId,
|
||||||
|
environmentId,
|
||||||
|
workspaceId,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ensure the config is refreshed after the action is done
|
// Ensure the config is refreshed after the action is done
|
||||||
|
|||||||
Reference in New Issue
Block a user