feat: openssl based hoppscotch-relay for request forwarding (#4442)
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
<template>
|
||||
<HoppSmartModal
|
||||
v-if="show"
|
||||
dialog
|
||||
:title="t('agent.ca_certs')"
|
||||
@close="emit('hide-modal')"
|
||||
>
|
||||
<template #body>
|
||||
<div class="flex flex-col space-y-4">
|
||||
<ul
|
||||
v-if="certificates.length > 0"
|
||||
class="mx-4 border border-dividerDark rounded"
|
||||
>
|
||||
<li
|
||||
v-for="(certificate, index) in certificates"
|
||||
:key="index"
|
||||
class="flex border-dividerDark px-2 items-center justify-between"
|
||||
:class="{ 'border-t border-dividerDark': index !== 0 }"
|
||||
>
|
||||
<div class="truncate">
|
||||
{{ certificate.filename }}
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="certificate.enabled ? IconCheckCircle : IconCircle"
|
||||
:title="
|
||||
certificate.enabled
|
||||
? t('action.turn_off')
|
||||
: t('action.turn_on')
|
||||
"
|
||||
color="green"
|
||||
@click="toggleEntryEnabled(index)"
|
||||
/>
|
||||
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconTrash"
|
||||
:title="t('action.remove')"
|
||||
@click="deleteEntry(index)"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<HoppButtonSecondary
|
||||
class="mx-4"
|
||||
:icon="IconPlus"
|
||||
:label="t('agent.add_cert_file')"
|
||||
:loading="selectedFiles && selectedFiles!.length > 0"
|
||||
filled
|
||||
outline
|
||||
@click="openFilePicker"
|
||||
/>
|
||||
|
||||
<p class="text-center text-secondaryLight">
|
||||
Hoppscotch supports .crt, .cer or .pem files containing one or more
|
||||
certificates.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex space-x-2">
|
||||
<HoppButtonPrimary :label="t('action.save')" @click="save" />
|
||||
<HoppButtonSecondary
|
||||
:label="t('action.cancel')"
|
||||
filled
|
||||
outline
|
||||
@click="emit('hide-modal')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</HoppSmartModal>
|
||||
</template>
|
||||
|
||||
<!-- TODO: i18n -->
|
||||
<script setup lang="ts">
|
||||
import IconPlus from "~icons/lucide/plus"
|
||||
import IconCheckCircle from "~icons/lucide/check-circle"
|
||||
import IconCircle from "~icons/lucide/circle"
|
||||
import IconTrash from "~icons/lucide/trash"
|
||||
import { useService } from "dioc/vue"
|
||||
import { ref, watch } from "vue"
|
||||
import { useFileDialog } from "@vueuse/core"
|
||||
import { cloneDeep } from "lodash-es"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import {
|
||||
CACertificateEntry,
|
||||
AgentInterceptorService,
|
||||
} from "~/platform/std/interceptors/agent"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { hasValidExtension } from "~/helpers/utils/file-extension"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "hide-modal"): void
|
||||
}>()
|
||||
|
||||
const nativeInterceptorService = useService(AgentInterceptorService)
|
||||
|
||||
const certificates = ref<CACertificateEntry[]>([])
|
||||
|
||||
const {
|
||||
files: selectedFiles,
|
||||
open: openFilePicker,
|
||||
reset: resetFilePicker,
|
||||
onChange: onSelectedFilesChange,
|
||||
} = useFileDialog({
|
||||
multiple: true,
|
||||
})
|
||||
|
||||
const ALLOWED_EXTENSIONS = [".crt", ".cer", ".pem"]
|
||||
|
||||
function isValidCertType(filename: string): boolean {
|
||||
return hasValidExtension(filename, ALLOWED_EXTENSIONS)
|
||||
}
|
||||
|
||||
// When files are selected, add them to the list of certificates and reset the file list
|
||||
onSelectedFilesChange(async (files) => {
|
||||
if (files) {
|
||||
const addedCertificates: CACertificateEntry[] = []
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i]
|
||||
|
||||
if (!isValidCertType(file.name)) {
|
||||
toast.error(t("error.invalid_file_type", { filename: file.name }))
|
||||
continue
|
||||
}
|
||||
|
||||
const data = new Uint8Array(await file.arrayBuffer())
|
||||
|
||||
addedCertificates.push({
|
||||
filename: file.name,
|
||||
enabled: true,
|
||||
certificate: data,
|
||||
})
|
||||
}
|
||||
|
||||
certificates.value.push(...addedCertificates)
|
||||
|
||||
resetFilePicker()
|
||||
}
|
||||
})
|
||||
|
||||
// When the modal is shown, clone the certificates from the service,
|
||||
// We only write to the service when the user clicks on save
|
||||
watch(
|
||||
() => props.show,
|
||||
(show) => {
|
||||
if (show) {
|
||||
certificates.value = cloneDeep(
|
||||
nativeInterceptorService.caCertificates.value
|
||||
)
|
||||
} else {
|
||||
resetFilePicker()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
function save() {
|
||||
nativeInterceptorService.caCertificates.value = certificates.value
|
||||
emit("hide-modal")
|
||||
}
|
||||
|
||||
function deleteEntry(index: number) {
|
||||
certificates.value.splice(index, 1)
|
||||
}
|
||||
|
||||
function toggleEntryEnabled(index: number) {
|
||||
certificates.value[index].enabled = !certificates.value[index].enabled
|
||||
}
|
||||
</script>
|
||||
@@ -9,37 +9,29 @@
|
||||
</div>
|
||||
|
||||
<div class="flex space-x-4">
|
||||
<!--
|
||||
<HoppButtonSecondary
|
||||
:icon="IconLucideFileBadge"
|
||||
:label="'CA Certificates'"
|
||||
outline
|
||||
@click="showCACertificatesModal = true"
|
||||
/>
|
||||
-->
|
||||
<!--
|
||||
<HoppButtonSecondary
|
||||
:icon="IconLucideFileKey"
|
||||
:label="t('agent.client_certs')"
|
||||
outline
|
||||
@click="showClientCertificatesModal = true"
|
||||
/>
|
||||
-->
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<ModalsNativeCACertificates
|
||||
<InterceptorsAgentModalNativeCACertificates
|
||||
:show="showCACertificatesModal"
|
||||
@hide-modal="showCACertificatesModal = false"
|
||||
/>
|
||||
-->
|
||||
|
||||
<!--
|
||||
<InterceptorsAgentModalNativeClientCertificates
|
||||
:show="showClientCertificatesModal"
|
||||
@hide-modal="showClientCertificatesModal = false"
|
||||
/>
|
||||
-->
|
||||
|
||||
<div class="pt-4 space-y-4">
|
||||
<div class="flex items-center">
|
||||
@@ -68,7 +60,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
// import IconLucideFileKey from "~icons/lucide/file-key"
|
||||
import IconLucideFileKey from "~icons/lucide/file-key"
|
||||
import { useService } from "dioc/vue"
|
||||
import {
|
||||
RequestDef,
|
||||
@@ -84,8 +76,8 @@ const agentInterceptorService = useService(AgentInterceptorService)
|
||||
|
||||
const allowSSLVerification = agentInterceptorService.validateCerts
|
||||
|
||||
// const showCACertificatesModal = ref(false)
|
||||
// const showClientCertificatesModal = ref(false)
|
||||
const showCACertificatesModal = ref(false)
|
||||
const showClientCertificatesModal = ref(false)
|
||||
|
||||
const allowProxy = ref(false)
|
||||
const proxyURL = ref("")
|
||||
|
||||
Reference in New Issue
Block a user