Skip to content

Commit

Permalink
feat(pki): prompt for pkcs12 password
Browse files Browse the repository at this point in the history
  • Loading branch information
amphineko committed Mar 17, 2024
1 parent ca4a8b0 commit accca31
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 22 deletions.
123 changes: 123 additions & 0 deletions packages/web/app/pki/exportDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"use client"

import { FileDownload } from "@mui/icons-material"
import {
Button,
CircularProgress,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Stack,
TextField,
} from "@mui/material"
import { SerialNumberString } from "@yonagi/common/types/pki/SerialNumberString"
import { FormEvent, useState } from "react"
import { useQuery } from "react-query"

import { exportClientCertificateP12 } from "./actions"
import { base64ToBlob, downloadBlob } from "../../lib/client"
import { useNotifications } from "../../lib/notifications"

export function ExportPkcs12Dialog({
onClose,
open,
serialNumber,
}: {
onClose: () => void
open: boolean
serialNumber: SerialNumberString
}): JSX.Element {
const [password, setPassword] = useState("")

const { isLoading, refetch } = useQuery<unknown, unknown, { password: string }>({
enabled: false,
queryFn: async () => {
const base64 = await exportClientCertificateP12(serialNumber, password)
const blob = base64ToBlob(base64, "application/x-pkcs12")
downloadBlob(blob, `${serialNumber}.p12`)
},
onError: (error) => {
notifyError("Failed to export PKCS#12", String(error))
},
queryKey: ["pki", "download", serialNumber],
retry: false,
})

const handleSubmit = () => {
refetch()
.then(() => {
onClose()
})
.catch((error) => {
notifyError("Failed to export as PKCS#12", String(error))
})
}

const { notifyError } = useNotifications()

return (
<Dialog
onClose={onClose}
open={open}
maxWidth="sm"
fullWidth
PaperProps={{
component: "form",
onSubmit: (e: FormEvent<HTMLFormElement>) => {
e.preventDefault()
handleSubmit()
},
}}
>
<DialogTitle>Export PKCS#12</DialogTitle>
<DialogContent>
<Stack spacing={2}>
<TextField
autoFocus
fullWidth
label="Password"
onChange={(e) => {
setPassword(e.currentTarget.value)
}}
required
type="password"
value={password}
variant="filled"
/>
</Stack>
</DialogContent>
<DialogActions>
<Button
disabled={isLoading || password.length === 0}
onClick={() => {
handleSubmit()
}}
startIcon={isLoading ? <CircularProgress size="1em" /> : <FileDownload />}
type="submit"
>
Export
</Button>
<Button onClick={onClose}>Cancel</Button>
</DialogActions>
</Dialog>
)
}

export function useExportPkcs12Dialog({ serialNumber }: { serialNumber: SerialNumberString }) {
const [isOpen, setOpen] = useState(false)
const dialog = ExportPkcs12Dialog({
onClose: () => {
setOpen(false)
},
open: isOpen,
serialNumber,
})

return {
dialog,
open: () => {
setOpen(true)
},
}
}
32 changes: 10 additions & 22 deletions packages/web/app/pki/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ import {
deleteClientCertificate,
deleteServerCertificate,
exportCertificateAuthorityPem,
exportClientCertificateP12,
getPkiSummary,
} from "./actions"
import { base64ToBlob, downloadBlob, useNonce, useQueryHelpers } from "../../lib/client"
import { useExportPkcs12Dialog } from "./exportDialog"
import { downloadBlob, useNonce, useQueryHelpers } from "../../lib/client"
import { ValidatedForm, ValidatedTextField } from "../../lib/forms"
import { useNotifications } from "../../lib/notifications"

Expand Down Expand Up @@ -109,20 +109,6 @@ function CertificateDisplayAccordionDetails({
})
const { notifyError } = useNotifications()

const { isLoading: isExportingP12, refetch: refetchP12 } = useQuery({
enabled: false,
queryFn: async () => {
const base64 = await exportClientCertificateP12(cert.serialNumber, "neko")
const blob = base64ToBlob(base64, "application/x-pkcs12")
downloadBlob(blob, `${cert.serialNumber}.p12`)
},
onError: (error) => {
notifyError("Failed to export PKCS#12", String(error))
},
queryKey: ["pki", "download", cert.serialNumber],
retry: false,
})

const { isLoading: isExportingCaPem, refetch: refetchCaPem } = useQuery({
enabled: false,
queryFn: async () => {
Expand All @@ -137,6 +123,10 @@ function CertificateDisplayAccordionDetails({
retry: false,
})

const { dialog: exportPkcs12Dialog, open: openExportPkcs12Dialog } = useExportPkcs12Dialog({
serialNumber: cert.serialNumber,
})

const [deletePopoverAnchor, setDeletePopoverAnchor] = useState<HTMLElement | null>(null)

return (
Expand Down Expand Up @@ -189,16 +179,13 @@ function CertificateDisplayAccordionDetails({
{canExportP12 && (
<Button
color="primary"
disabled={isExportingP12}
onClick={() => {
refetchP12().catch(() => {
/* */
})
openExportPkcs12Dialog()
}}
startIcon={isExportingP12 ? <CircularProgress size="1em" /> : <Download />}
startIcon={<Download />}
variant="contained"
>
Download
PKCS#12
</Button>
)}
</Stack>
Expand Down Expand Up @@ -231,6 +218,7 @@ function CertificateDisplayAccordionDetails({
Confirm Delete
</Button>
</Popover>
{canExportP12 && exportPkcs12Dialog}
</AccordionDetails>
)
}
Expand Down

0 comments on commit accca31

Please sign in to comment.