gRPC authentication

This commit is contained in:
Gregory Schier
2024-02-17 23:47:28 -08:00
parent ab8503d87c
commit 8d29fad261
16 changed files with 246 additions and 123 deletions

View File

@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "\n SELECT\n id, model, workspace_id, folder_id, created_at, updated_at, name, sort_priority,\n url, service, method, message,\n proto_files AS \"proto_files!: sqlx::types::Json<Vec<String>>\"\n FROM grpc_requests\n WHERE id = ?\n ",
"query": "\n SELECT\n id, model, workspace_id, folder_id, created_at, updated_at, name, sort_priority,\n url, service, method, message, authentication_type,\n authentication AS \"authentication!: Json<HashMap<String, JsonValue>>\",\n proto_files AS \"proto_files!: sqlx::types::Json<Vec<String>>\"\n FROM grpc_requests\n WHERE id = ?\n ",
"describe": {
"columns": [
{
@@ -64,9 +64,19 @@
"type_info": "Text"
},
{
"name": "proto_files!: sqlx::types::Json<Vec<String>>",
"name": "authentication_type",
"ordinal": 12,
"type_info": "Text"
},
{
"name": "authentication!: Json<HashMap<String, JsonValue>>",
"ordinal": 13,
"type_info": "Text"
},
{
"name": "proto_files!: sqlx::types::Json<Vec<String>>",
"ordinal": 14,
"type_info": "Text"
}
],
"parameters": {
@@ -85,8 +95,10 @@
true,
true,
false,
true,
false,
false
]
},
"hash": "7398403d3de2dc5c5b4b6392f083041d9a55194bb97819225a2612fdeb60ad42"
"hash": "0d9e685f878fc2a0e1803c6aaae3828deebd684fc9f78e9f8595a550f90749fe"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "\n SELECT\n id, model, workspace_id, folder_id, created_at, updated_at, name, sort_priority,\n url, service, method, message,\n proto_files AS \"proto_files!: sqlx::types::Json<Vec<String>>\"\n FROM grpc_requests\n WHERE workspace_id = ?\n ",
"query": "\n SELECT\n id, model, workspace_id, folder_id, created_at, updated_at, name, sort_priority,\n url, service, method, message, authentication_type,\n authentication AS \"authentication!: Json<HashMap<String, JsonValue>>\",\n proto_files AS \"proto_files!: sqlx::types::Json<Vec<String>>\"\n FROM grpc_requests\n WHERE workspace_id = ?\n ",
"describe": {
"columns": [
{
@@ -64,9 +64,19 @@
"type_info": "Text"
},
{
"name": "proto_files!: sqlx::types::Json<Vec<String>>",
"name": "authentication_type",
"ordinal": 12,
"type_info": "Text"
},
{
"name": "authentication!: Json<HashMap<String, JsonValue>>",
"ordinal": 13,
"type_info": "Text"
},
{
"name": "proto_files!: sqlx::types::Json<Vec<String>>",
"ordinal": 14,
"type_info": "Text"
}
],
"parameters": {
@@ -85,8 +95,10 @@
true,
true,
false,
true,
false,
false
]
},
"hash": "761d27c3ec425c37ad9abe9c732a9c1746c81ca50d2c413e540b74c8c8e908b7"
"hash": "545d21ff21bd02468be86746325dc9f1b50f3fe53d7735194d91927b5d14a436"
}

View File

@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n INSERT INTO grpc_requests (\n id, name, workspace_id, folder_id, sort_priority, url, service, method, message,\n proto_files, authentication_type, authentication\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n folder_id = excluded.folder_id,\n sort_priority = excluded.sort_priority,\n url = excluded.url,\n service = excluded.service,\n method = excluded.method,\n message = excluded.message,\n proto_files = excluded.proto_files,\n authentication_type = excluded.authentication_type,\n authentication = excluded.authentication\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 12
},
"nullable": []
},
"hash": "c554305252cb21e34aa1e3c1f204c6060c3d2a209689a879824dea4d26e5497e"
}

View File

@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "\n INSERT INTO grpc_requests (\n id, name, workspace_id, folder_id, sort_priority, url, service, method, message,\n proto_files\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n folder_id = excluded.folder_id,\n sort_priority = excluded.sort_priority,\n url = excluded.url,\n service = excluded.service,\n method = excluded.method,\n message = excluded.message,\n proto_files = excluded.proto_files\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 10
},
"nullable": []
},
"hash": "ee562f85ec28c554c607adde670fc30eaeffeed6883e712bda4b4d6ca49cf740"
}

View File

@@ -1,5 +1,6 @@
use std::collections::HashMap;
use std::path::PathBuf;
use std::str::FromStr;
use hyper::client::HttpConnector;
use hyper::Client;
@@ -10,8 +11,9 @@ use serde_json::Deserializer;
use tokio_stream::wrappers::ReceiverStream;
use tokio_stream::StreamExt;
use tonic::body::BoxBody;
use tonic::metadata::{MetadataKey, MetadataValue};
use tonic::transport::Uri;
use tonic::{IntoRequest, IntoStreamingRequest, Response, Status, Streaming};
use tonic::{IntoRequest, IntoStreamingRequest, Request, Response, Status, Streaming};
use crate::codec::DynamicCodec;
use crate::proto::{fill_pool, fill_pool_from_files, get_transport, method_desc_to_path};
@@ -47,6 +49,7 @@ impl GrpcConnection {
service: &str,
method: &str,
message: &str,
metadata: HashMap<String, String>,
) -> Result<DynamicMessage, String> {
let method = &self.method(&service, &method)?;
let input_message = method.input();
@@ -58,7 +61,9 @@ impl GrpcConnection {
let mut client = tonic::client::Grpc::with_origin(self.conn.clone(), self.uri.clone());
let req = req_message.into_request();
let mut req = req_message.into_request();
decorate_req(metadata, &mut req).map_err(|e| e.to_string())?;
let path = method_desc_to_path(method);
let codec = DynamicCodec::new(method.clone());
client.ready().await.unwrap();
@@ -75,12 +80,13 @@ impl GrpcConnection {
service: &str,
method: &str,
stream: ReceiverStream<String>,
metadata: HashMap<String, String>,
) -> Result<Result<Response<Streaming<DynamicMessage>>, Status>, String> {
let method = &self.method(&service, &method)?;
let mut client = tonic::client::Grpc::with_origin(self.conn.clone(), self.uri.clone());
let method2 = method.clone();
let req = stream
let mut req = stream
.map(move |s| {
let mut deserializer = Deserializer::from_str(&s);
let req_message = DynamicMessage::deserialize(method2.input(), &mut deserializer)
@@ -90,6 +96,9 @@ impl GrpcConnection {
req_message
})
.into_streaming_request();
decorate_req(metadata, &mut req).map_err(|e| e.to_string())?;
let path = method_desc_to_path(method);
let codec = DynamicCodec::new(method.clone());
client.ready().await.map_err(|e| e.to_string())?;
@@ -101,11 +110,12 @@ impl GrpcConnection {
service: &str,
method: &str,
stream: ReceiverStream<String>,
metadata: HashMap<String, String>,
) -> Result<DynamicMessage, String> {
let method = &self.method(&service, &method)?;
let mut client = tonic::client::Grpc::with_origin(self.conn.clone(), self.uri.clone());
let req = {
let mut req = {
let method = method.clone();
stream
.map(move |s| {
@@ -119,6 +129,9 @@ impl GrpcConnection {
})
.into_streaming_request()
};
decorate_req(metadata, &mut req).map_err(|e| e.to_string())?;
let path = method_desc_to_path(method);
let codec = DynamicCodec::new(method.clone());
client.ready().await.unwrap();
@@ -134,6 +147,7 @@ impl GrpcConnection {
service: &str,
method: &str,
message: &str,
metadata: HashMap<String, String>,
) -> Result<Result<Response<Streaming<DynamicMessage>>, Status>, String> {
let method = &self.method(&service, &method)?;
let input_message = method.input();
@@ -145,7 +159,9 @@ impl GrpcConnection {
let mut client = tonic::client::Grpc::with_origin(self.conn.clone(), self.uri.clone());
let req = req_message.into_request();
let mut req = req_message.into_request();
decorate_req(metadata, &mut req).map_err(|e| e.to_string())?;
let path = method_desc_to_path(method);
let codec = DynamicCodec::new(method.clone());
client.ready().await.map_err(|e| e.to_string())?;
@@ -222,10 +238,11 @@ impl GrpcHandle {
service: &str,
method: &str,
message: &str,
metadata: HashMap<String, String>,
) -> Result<Result<Response<Streaming<DynamicMessage>>, Status>, String> {
self.connect(id, uri, proto_files)
.await?
.server_streaming(service, method, message)
.server_streaming(service, method, message, metadata)
.await
}
@@ -237,10 +254,11 @@ impl GrpcHandle {
service: &str,
method: &str,
stream: ReceiverStream<String>,
metadata: HashMap<String, String>,
) -> Result<DynamicMessage, String> {
self.connect(id, uri, proto_files)
.await?
.client_streaming(service, method, stream)
.client_streaming(service, method, stream, metadata)
.await
}
@@ -252,10 +270,11 @@ impl GrpcHandle {
service: &str,
method: &str,
stream: ReceiverStream<String>,
metadata: HashMap<String, String>,
) -> Result<Result<Response<Streaming<DynamicMessage>>, Status>, String> {
self.connect(id, uri, proto_files)
.await?
.streaming(service, method, stream)
.streaming(service, method, stream, metadata)
.await
}
@@ -282,3 +301,13 @@ impl GrpcHandle {
Ok(connection)
}
}
fn decorate_req<T>(metadata: HashMap<String, String>, req: &mut Request<T>) -> Result<(), String> {
for (k, v) in metadata {
req.metadata_mut().insert(
MetadataKey::from_str(k.as_str()).map_err(|e| e.to_string())?,
MetadataValue::from_str(v.as_str()).map_err(|e| e.to_string())?,
);
}
Ok(())
}

View File

@@ -0,0 +1,2 @@
ALTER TABLE grpc_requests ADD COLUMN authentication TEXT NOT NULL DEFAULT '{}';
ALTER TABLE grpc_requests ADD COLUMN authentication_type TEXT;

View File

@@ -17,6 +17,7 @@ use std::str::FromStr;
use ::http::uri::InvalidUri;
use ::http::Uri;
use base64::Engine;
use fern::colors::ColoredLevelConfig;
use log::{debug, error, info, warn};
use rand::random;
@@ -128,12 +129,20 @@ async fn cmd_grpc_reflect(
#[tauri::command]
async fn cmd_grpc_go(
request_id: &str,
environment_id: Option<&str>,
w: Window,
grpc_handle: State<'_, Mutex<GrpcHandle>>,
) -> Result<String, String> {
let req = get_grpc_request(&w, request_id)
.await
.map_err(|e| e.to_string())?;
let environment = match environment_id {
Some(id) => Some(get_environment(&w, id).await.map_err(|e| e.to_string())?),
None => None,
};
let workspace = get_workspace(&w, &req.workspace_id)
.await
.map_err(|e| e.to_string())?;
let conn = {
let req = req.clone();
upsert_grpc_connection(
@@ -261,6 +270,37 @@ async fn cmd_grpc_go(
}
};
let event_handler = w.listen_global(format!("grpc_client_msg_{}", conn.id).as_str(), cb);
let mut metadata = HashMap::new();
if let Some(b) = &req.authentication_type {
let req = req.clone();
let environment_ref = environment.as_ref();
let empty_value = &serde_json::to_value("").unwrap();
let a = req.authentication.0;
if b == "basic" {
let raw_username = a
.get("username")
.unwrap_or(empty_value)
.as_str()
.unwrap_or("");
let raw_password = a
.get("password")
.unwrap_or(empty_value)
.as_str()
.unwrap_or("");
let username = render::render(raw_username, &workspace, environment_ref);
let password = render::render(raw_password, &workspace, environment_ref);
let auth = format!("{username}:{password}");
let encoded = base64::engine::general_purpose::STANDARD_NO_PAD.encode(auth);
metadata.insert("Authorization".to_string(), format!("Basic {}", encoded));
} else if b == "bearer" {
let raw_token = a.get("token").unwrap_or(empty_value).as_str().unwrap_or("");
let token = render::render(raw_token, &workspace, environment_ref);
metadata.insert("Authorization".to_string(), format!("Bearer {token}"));
}
}
println!("METADATA: {:?}", metadata);
let grpc_listen = {
let w = w.clone();
@@ -272,28 +312,36 @@ async fn cmd_grpc_go(
method_desc.is_server_streaming(),
) {
(true, true) => (
Some(connection.streaming(&service, &method, in_msg_stream).await),
Some(
connection
.streaming(&service, &method, in_msg_stream, metadata)
.await,
),
None,
),
(true, false) => (
None,
Some(
connection
.client_streaming(&service, &method, in_msg_stream)
.client_streaming(&service, &method, in_msg_stream, metadata)
.await,
),
),
(false, true) => (
Some(
connection
.server_streaming(&service, &method, &req.message)
.server_streaming(&service, &method, &req.message, metadata)
.await,
),
None,
),
(false, false) => (
None,
Some(connection.unary(&service, &method, &req.message).await),
Some(
connection
.unary(&service, &method, &req.message, metadata)
.await,
),
),
};

View File

@@ -205,6 +205,8 @@ pub struct GrpcRequest {
pub method: Option<String>,
pub message: String,
pub proto_files: Json<Vec<String>>,
pub authentication_type: Option<String>,
pub authentication: Json<HashMap<String, JsonValue>>,
}
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize, Default)]
@@ -502,9 +504,9 @@ pub async fn upsert_grpc_request(
r#"
INSERT INTO grpc_requests (
id, name, workspace_id, folder_id, sort_priority, url, service, method, message,
proto_files
proto_files, authentication_type, authentication
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (id) DO UPDATE SET
updated_at = CURRENT_TIMESTAMP,
name = excluded.name,
@@ -514,7 +516,9 @@ pub async fn upsert_grpc_request(
service = excluded.service,
method = excluded.method,
message = excluded.message,
proto_files = excluded.proto_files
proto_files = excluded.proto_files,
authentication_type = excluded.authentication_type,
authentication = excluded.authentication
"#,
id,
trimmed_name,
@@ -526,6 +530,8 @@ pub async fn upsert_grpc_request(
request.method,
request.message,
request.proto_files,
request.authentication_type,
request.authentication,
)
.execute(&db)
.await?;
@@ -546,7 +552,8 @@ pub async fn get_grpc_request(
r#"
SELECT
id, model, workspace_id, folder_id, created_at, updated_at, name, sort_priority,
url, service, method, message,
url, service, method, message, authentication_type,
authentication AS "authentication!: Json<HashMap<String, JsonValue>>",
proto_files AS "proto_files!: sqlx::types::Json<Vec<String>>"
FROM grpc_requests
WHERE id = ?
@@ -567,7 +574,8 @@ pub async fn list_grpc_requests(
r#"
SELECT
id, model, workspace_id, folder_id, created_at, updated_at, name, sort_priority,
url, service, method, message,
url, service, method, message, authentication_type,
authentication AS "authentication!: Json<HashMap<String, JsonValue>>",
proto_files AS "proto_files!: sqlx::types::Json<Vec<String>>"
FROM grpc_requests
WHERE workspace_id = ?

View File

@@ -1,49 +1,64 @@
import { useUpdateGrpcRequest } from '../hooks/useUpdateGrpcRequest';
import { useUpdateHttpRequest } from '../hooks/useUpdateHttpRequest';
import type { HttpRequest } from '../lib/models';
import type { GrpcRequest, HttpRequest } from '../lib/models';
import { Input } from './core/Input';
import { VStack } from './core/Stacks';
interface Props {
requestId: string;
authentication: HttpRequest['authentication'];
interface Props<T> {
request: T;
}
export function BasicAuth({ requestId, authentication }: Props) {
const updateRequest = useUpdateHttpRequest(requestId);
export function BasicAuth<T extends HttpRequest | GrpcRequest>({ request }: Props<T>) {
const updateHttpRequest = useUpdateHttpRequest(request.id);
const updateGrpcRequest = useUpdateGrpcRequest(request.id);
return (
<VStack className="my-2" space={2}>
<Input
useTemplating
autocompleteVariables
forceUpdateKey={requestId}
forceUpdateKey={request.id}
placeholder="username"
label="Username"
name="username"
size="sm"
defaultValue={`${authentication.username}`}
defaultValue={`${request.authentication.username}`}
onChange={(username: string) => {
updateRequest.mutate((r) => ({
...r,
authentication: { password: r.authentication.password, username },
}));
if (request.model === 'http_request') {
updateHttpRequest.mutate((r) => ({
...r,
authentication: { password: r.authentication.password, username },
}));
} else {
updateGrpcRequest.mutate((r) => ({
...r,
authentication: { password: r.authentication.password, username },
}));
}
}}
/>
<Input
useTemplating
autocompleteVariables
forceUpdateKey={requestId}
forceUpdateKey={request?.id}
placeholder="password"
label="Password"
name="password"
size="sm"
type="password"
defaultValue={`${authentication.password}`}
defaultValue={`${request.authentication.password}`}
onChange={(password: string) => {
updateRequest.mutate((r) => ({
...r,
authentication: { username: r.authentication.username, password },
}));
if (request.model === 'http_request') {
updateHttpRequest.mutate((r) => ({
...r,
authentication: { username: r.authentication.username, password },
}));
} else {
updateGrpcRequest.mutate((r) => ({
...r,
authentication: { username: r.authentication.username, password },
}));
}
}}
/>
</VStack>

View File

@@ -1,15 +1,16 @@
import { useUpdateGrpcRequest } from '../hooks/useUpdateGrpcRequest';
import { useUpdateHttpRequest } from '../hooks/useUpdateHttpRequest';
import type { HttpRequest } from '../lib/models';
import type { GrpcRequest, HttpRequest } from '../lib/models';
import { Input } from './core/Input';
import { VStack } from './core/Stacks';
interface Props {
requestId: string;
authentication: HttpRequest['authentication'];
interface Props<T> {
request: T;
}
export function BearerAuth({ requestId, authentication }: Props) {
const updateRequest = useUpdateHttpRequest(requestId);
export function BearerAuth<T extends HttpRequest | GrpcRequest>({ request }: Props<T>) {
const updateHttpRequest = useUpdateHttpRequest(request?.id ?? null);
const updateGrpcRequest = useUpdateGrpcRequest(request?.id ?? null);
return (
<VStack className="my-2" space={2}>
@@ -21,12 +22,19 @@ export function BearerAuth({ requestId, authentication }: Props) {
label="Token"
name="token"
size="sm"
defaultValue={`${authentication.token}`}
defaultValue={`${request.authentication.token}`}
onChange={(token: string) => {
updateRequest.mutate((r) => ({
...r,
authentication: { token },
}));
if (request.model === 'http_request') {
updateHttpRequest.mutate((r) => ({
...r,
authentication: { token },
}));
} else {
updateGrpcRequest.mutate((r) => ({
...r,
authentication: { token },
}));
}
}}
/>
</VStack>

View File

@@ -80,10 +80,7 @@ export function GrpcConnectionLayout({ style }: Props) {
style={style}
activeRequest={activeRequest}
methodType={methodType}
onUnary={grpc.unary.mutate}
onServerStreaming={grpc.serverStreaming.mutate}
onClientStreaming={grpc.clientStreaming.mutate}
onStreaming={grpc.streaming.mutate}
onGo={grpc.go.mutate}
onCommit={grpc.commit.mutate}
onCancel={grpc.cancel.mutate}
onSend={grpc.send.mutate}
@@ -93,7 +90,7 @@ export function GrpcConnectionLayout({ style }: Props) {
/>
)}
secondSlot={({ style }) =>
!grpc.unary.isLoading && (
!grpc.go.isLoading && (
<div
style={style}
className={classNames(
@@ -102,9 +99,9 @@ export function GrpcConnectionLayout({ style }: Props) {
'shadow shadow-gray-100 dark:shadow-gray-0 relative',
)}
>
{grpc.unary.error ? (
{grpc.go.error ? (
<Banner color="danger" className="m-2">
{grpc.unary.error}
{grpc.go.error}
</Banner>
) : messages.length >= 0 ? (
<GrpcConnectionMessagesPane activeRequest={activeRequest} methodType={methodType} />

View File

@@ -5,9 +5,12 @@ import React, { useCallback, useMemo, useRef, useState } from 'react';
import { createGlobalState } from 'react-use';
import type { ReflectResponseService } from '../hooks/useGrpc';
import { useGrpcConnections } from '../hooks/useGrpcConnections';
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
import { useUpdateGrpcRequest } from '../hooks/useUpdateGrpcRequest';
import type { GrpcRequest } from '../lib/models';
import { AUTH_TYPE_BASIC, AUTH_TYPE_BEARER, AUTH_TYPE_NONE } from '../lib/models';
import { BasicAuth } from './BasicAuth';
import { BearerAuth } from './BearerAuth';
import { Button } from './core/Button';
import { Icon } from './core/Icon';
import { IconButton } from './core/IconButton';
@@ -15,6 +18,7 @@ import { RadioDropdown } from './core/RadioDropdown';
import { HStack, VStack } from './core/Stacks';
import type { TabItem } from './core/Tabs/Tabs';
import { TabContent, Tabs } from './core/Tabs/Tabs';
import { EmptyStateText } from './EmptyStateText';
import { GrpcEditor } from './GrpcEditor';
import { UrlBar } from './UrlBar';
@@ -31,13 +35,10 @@ interface Props {
| 'streaming'
| 'no-schema'
| 'no-method';
onUnary: () => void;
onCommit: () => void;
onCancel: () => void;
onSend: (v: { message: string }) => void;
onClientStreaming: () => void;
onServerStreaming: () => void;
onStreaming: () => void;
onGo: () => void;
services: ReflectResponseService[] | null;
}
@@ -50,19 +51,17 @@ export function GrpcConnectionSetupPane({
activeRequest,
reflectionError,
reflectionLoading,
onStreaming,
onClientStreaming,
onServerStreaming,
onGo,
onCommit,
onCancel,
onSend,
onUnary,
}: Props) {
const connections = useGrpcConnections(activeRequest.id ?? null);
const updateRequest = useUpdateGrpcRequest(activeRequest?.id ?? null);
const activeConnection = connections[0] ?? null;
const isStreaming = activeConnection?.elapsed === 0;
const [activeTab, setActiveTab] = useActiveTab();
const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest.id ?? null);
const [paneSize, setPaneSize] = useState(99999);
const urlContainerEl = useRef<HTMLDivElement>(null);
@@ -116,17 +115,9 @@ export function GrpcConnectionSetupPane({
body: 'Service or method not selected',
});
}
if (methodType === 'streaming') {
onStreaming();
} else if (methodType === 'server_streaming') {
onServerStreaming();
} else if (methodType === 'client_streaming') {
onClientStreaming();
} else {
onUnary();
}
onGo();
},
[activeRequest, methodType, onStreaming, onServerStreaming, onClientStreaming, onUnary],
[activeRequest, onGo],
);
const tabs: TabItem[] = useMemo(
@@ -136,7 +127,7 @@ export function GrpcConnectionSetupPane({
value: 'auth',
label: 'Auth',
options: {
value: AUTH_TYPE_NONE, // TODO
value: activeRequest.authenticationType,
items: [
{ label: 'Basic Auth', shortLabel: 'Basic', value: AUTH_TYPE_BASIC },
{ label: 'Bearer Token', shortLabel: 'Bearer', value: AUTH_TYPE_BEARER },
@@ -144,13 +135,24 @@ export function GrpcConnectionSetupPane({
{ label: 'No Authentication', shortLabel: 'Auth', value: AUTH_TYPE_NONE },
],
onChange: async (authenticationType) => {
// TODO
let authentication: GrpcRequest['authentication'] = activeRequest.authentication;
if (authenticationType === AUTH_TYPE_BASIC) {
authentication = {
username: authentication.username ?? '',
password: authentication.password ?? '',
};
} else if (authenticationType === AUTH_TYPE_BEARER) {
authentication = {
token: authentication.token ?? '',
};
}
await updateRequest.mutateAsync({ authenticationType, authentication });
},
},
},
{ value: 'metadata', label: 'Metadata' },
],
[],
[activeRequest.authentication, activeRequest.authenticationType, updateRequest],
);
return (
@@ -166,7 +168,7 @@ export function GrpcConnectionSetupPane({
url={activeRequest.url ?? ''}
method={null}
submitIcon={null}
forceUpdateKey={activeRequest?.id ?? ''}
forceUpdateKey={forceUpdateKey}
placeholder="localhost:50051"
onSubmit={handleConnect}
onUrlChange={handleChangeUrl}
@@ -270,6 +272,15 @@ export function GrpcConnectionSetupPane({
request={activeRequest}
/>
</TabContent>
<TabContent value="auth">
{activeRequest.authenticationType === AUTH_TYPE_BASIC ? (
<BasicAuth key={forceUpdateKey} request={activeRequest} />
) : activeRequest.authenticationType === AUTH_TYPE_BEARER ? (
<BearerAuth key={forceUpdateKey} request={activeRequest} />
) : (
<EmptyStateText>No Authentication {activeRequest.authenticationType}</EmptyStateText>
)}
</TabContent>
</Tabs>
</VStack>
);

View File

@@ -154,7 +154,7 @@ export const RequestPane = memo(function RequestPane({
token: authentication.token ?? '',
};
}
updateRequest.mutate({ authenticationType, authentication });
await updateRequest.mutateAsync({ authenticationType, authentication });
},
},
},
@@ -226,17 +226,9 @@ export const RequestPane = memo(function RequestPane({
>
<TabContent value="auth">
{activeRequest.authenticationType === AUTH_TYPE_BASIC ? (
<BasicAuth
key={forceUpdateKey}
requestId={activeRequest.id}
authentication={activeRequest.authentication}
/>
<BasicAuth key={forceUpdateKey} request={activeRequest} />
) : activeRequest.authenticationType === AUTH_TYPE_BEARER ? (
<BearerAuth
key={forceUpdateKey}
requestId={activeRequest.id}
authentication={activeRequest.authentication}
/>
<BearerAuth key={forceUpdateKey} request={activeRequest} />
) : (
<EmptyStateText>
No Authentication {activeRequest.authenticationType}

View File

@@ -82,7 +82,7 @@ export function Tabs({
{tabs.map((t) => {
const isActive = t.value === value;
const btnClassName = classNames(
isActive ? '' : 'text-gray-600 hover:text-gray-800',
isActive ? 'text-gray-800' : 'text-gray-600 hover:text-gray-700',
'!px-2 ml-[1px]',
);

View File

@@ -3,6 +3,7 @@ import { invoke } from '@tauri-apps/api';
import { emit } from '@tauri-apps/api/event';
import { minPromiseMillis } from '../lib/minPromiseMillis';
import type { GrpcConnection, GrpcRequest } from '../lib/models';
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
import { useDebouncedValue } from './useDebouncedValue';
export interface ReflectResponseService {
@@ -12,21 +13,10 @@ export interface ReflectResponseService {
export function useGrpc(req: GrpcRequest | null, conn: GrpcConnection | null) {
const requestId = req?.id ?? 'n/a';
const environmentId = useActiveEnvironmentId();
const unary = useMutation<void, string>({
mutationFn: async () => await invoke('cmd_grpc_go', { requestId }),
});
const clientStreaming = useMutation<void, string>({
mutationFn: async () => await invoke('cmd_grpc_go', { requestId }),
});
const serverStreaming = useMutation<void, string>({
mutationFn: async () => await invoke('cmd_grpc_go', { requestId }),
});
const streaming = useMutation<void, string>({
mutationFn: async () => await invoke('cmd_grpc_go', { requestId }),
const go = useMutation<void, string>({
mutationFn: async () => await invoke('cmd_grpc_go', { requestId, environmentId }),
});
const send = useMutation({
@@ -58,10 +48,7 @@ export function useGrpc(req: GrpcRequest | null, conn: GrpcConnection | null) {
});
return {
unary,
clientStreaming,
serverStreaming,
streaming,
go,
reflect,
cancel,
commit,

View File

@@ -115,6 +115,8 @@ export interface GrpcRequest extends BaseModel {
method: string | null;
message: string;
protoFiles: string[];
authentication: Record<string, string | number | boolean | null | undefined>;
authenticationType: string | null;
}
export interface GrpcMessage extends BaseModel {