Skip to content

Commit

Permalink
submit and revoke certificate
Browse files Browse the repository at this point in the history
  • Loading branch information
kayra1 committed Jun 24, 2024
1 parent 3a34467 commit 42cfe7e
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 5 deletions.
85 changes: 84 additions & 1 deletion ui/src/app/certificate_requests/components.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { Dispatch, SetStateAction, useState } from "react"
import { Dispatch, SetStateAction, useState, ChangeEvent } from "react"
import { useMutation, useQueryClient } from "react-query"
import { ConfirmationModalData } from "./row"
import { extractCert } from "../utils"
import { postCertToID } from "../queries"

interface ConfirmationModalProps {
modalData: ConfirmationModalData
Expand Down Expand Up @@ -28,6 +31,86 @@ export function ConfirmationModal({ modalData, setModalData }: ConfirmationModal
)
}

function SubmitCertificate({ certText, onClickFunc }: { certText: string, onClickFunc: any }) {
let certIsValid = false
try {
extractCert(certText.trim())
//TODO: compare public keys here too
certIsValid = true
}
catch { }

const validationComponent = certText == "" ? <></> : certIsValid ? <div><i className="p-icon--success"></i>Valid Certificate</div> : <div><i className="p-icon--error"></i>Invalid Certificate</div>
const buttonComponent = certIsValid ? <button className="p-button--positive" name="submit" onClick={onClickFunc} >Submit</button> : <button className="p-button--positive" name="submit" disabled={true} onClick={onClickFunc} >Submit</button>
return (
<>
{validationComponent}
{buttonComponent}
</>
)
}

interface SubmitCertificateModalProps {
id: string
setFormOpen: Dispatch<SetStateAction<boolean>>
}
export function SubmitCertificateModal({ id, setFormOpen }: SubmitCertificateModalProps) {
const queryClient = useQueryClient()
const mutation = useMutation(postCertToID(id), {
onSuccess: () => {
queryClient.invalidateQueries('csrs')
},
})
const [certificatePEMString, setCertificatePEMString] = useState<string>("")
const handleTextChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
setCertificatePEMString(event.target.value);
}
const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0]
if (file) {
const reader = new FileReader();
reader.onload = (e: ProgressEvent<FileReader>) => {
if (e.target) {
if (e.target.result) {
setCertificatePEMString(e.target.result.toString());
}
}
};
reader.readAsText(file);
}
};
const handleSubmit = () => {
mutation.mutate(certificatePEMString)
setFormOpen(false)
}
return (
<div className="p-modal" id="modal">
<section className="p-modal__dialog" role="dialog" aria-modal="true" aria-labelledby="modal-title" aria-describedby="modal-description">
<header className="p-modal__header">
<h2 className="p-modal__title" id="modal-title">Submit Certificate</h2>
</header>
<form className="p-form p-form--stacked">
<div className="p-form__group row">
<label htmlFor="textarea">
Enter or upload the Certificate in PEM format below
</label>
<textarea id="csr-textarea" name="textarea" rows={10} placeholder="-----BEGIN CERTIFICATE-----" onChange={handleTextChange} value={certificatePEMString} />
</div>
<div className="p-form__group row">
<input type="file" name="upload" accept=".pem" onChange={handleFileChange}></input>
</div>
<div className="p-form__group row">
</div>
</form>
<footer className="p-modal__footer">
<SubmitCertificate certText={certificatePEMString} onClickFunc={handleSubmit} />
<button className="u-no-margin--bottom" aria-controls="modal" onMouseDown={() => setFormOpen(false)}>Cancel</button>
</footer>
</section>
</div>
)
}

export function SuccessNotification({ successMessage }: { successMessage: string }) {
return (
<div className="p-notification--positive is-inline is-borderless u-no-margin--bottom">
Expand Down
23 changes: 19 additions & 4 deletions ui/src/app/certificate_requests/row.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useState, Dispatch, SetStateAction, useEffect, useRef } from "react"
import { UseMutationResult, useMutation, useQueryClient } from "react-query"
import { extractCSR, extractCert } from "../utils"
import { deleteCSR, rejectCSR } from "../queries"
import { ConfirmationModal, SuccessNotification } from "./components"
import { deleteCSR, rejectCSR, revokeCertificate } from "../queries"
import { ConfirmationModal, SubmitCertificateModal, SuccessNotification } from "./components"

type rowProps = {
id: number,
Expand All @@ -20,6 +20,7 @@ export type ConfirmationModalData = {
export default function Row({ id, csr, certificate, ActionMenuExpanded, setActionMenuExpanded }: rowProps) {
const [successNotification, setSuccessNotification] = useState<string | null>(null)
const [detailsMenuOpen, setDetailsMenuOpen] = useState<boolean>(false)
const [certificateFormOpen, setCertificateFormOpen] = useState<boolean>(false)
const [confirmationModalData, setConfirmationModalData] = useState<ConfirmationModalData>(null)

const csrObj = extractCSR(csr)
Expand All @@ -32,6 +33,9 @@ export default function Row({ id, csr, certificate, ActionMenuExpanded, setActio
const rejectMutation = useMutation(rejectCSR, {
onSuccess: () => queryClient.invalidateQueries('csrs')
})
const revokeMutation = useMutation(revokeCertificate, {
onSuccess: () => queryClient.invalidateQueries('csrs')
})

const mutationFunc = (mutation: UseMutationResult<any, unknown, string, unknown>) => {
mutation.mutate(id.toString())
Expand All @@ -48,6 +52,13 @@ export default function Row({ id, csr, certificate, ActionMenuExpanded, setActio
warningText: "Deleting a Certificate Request means this row will be completely removed from the application. This action cannot be undone."
})
}
const handleRevoke = () => {
setConfirmationModalData({
func: () => mutationFunc(revokeMutation),
warningText: "Revoking a Certificate will delete it from the table. This action cannot be undone."
})
}

const handleCopy = () => {
navigator.clipboard.writeText(csr).then(function () {
setSuccessNotification("CSR copied to clipboard")
Expand Down Expand Up @@ -114,8 +125,11 @@ export default function Row({ id, csr, certificate, ActionMenuExpanded, setActio
<button className="p-contextual-menu__link" onMouseDown={handleDelete}>Delete Certificate Request</button>
</span>
<span className="p-contextual-menu__group">
<button className="p-contextual-menu__link">Upload Certificate</button>
<button className="p-contextual-menu__link">Revoke Certificate</button>
<button className="p-contextual-menu__link" onMouseDown={() => setCertificateFormOpen(true)}>Upload Certificate</button>
{certificate == "rejected" || certificate == "" ?
<button className="p-contextual-menu__link" disabled={true} onMouseDown={handleRevoke}>Revoke Certificate</button> :
<button className="p-contextual-menu__link" onMouseDown={handleRevoke}>Revoke Certificate</button>
}
</span>
</span>
</span>
Expand All @@ -129,6 +143,7 @@ export default function Row({ id, csr, certificate, ActionMenuExpanded, setActio
</td>
</tr>
{confirmationModalData != null && <ConfirmationModal modalData={confirmationModalData} setModalData={setConfirmationModalData} />}
{certificateFormOpen && <SubmitCertificateModal id={id.toString()} setFormOpen={setCertificateFormOpen} />}
{successNotification &&
<tr>
<td>
Expand Down
26 changes: 26 additions & 0 deletions ui/src/app/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,22 @@ export async function postCSR(csr: string) {
return response.json()
}

export function postCertToID(id: string) {
return async (cert: string) => {
const response = await fetch("/api/v1/certificate_requests/" + id + "/certificate", {
method: 'post',
headers: {
'Content-Type': 'text/plain',
},
body: cert.trim()
})
if (!response.ok) {
throw new Error('Network response was not ok')
}
return response.json()
}
}

export async function deleteCSR(id: string) {
const response = await fetch("/api/v1/certificate_requests/" + id, {
method: 'delete',
Expand All @@ -40,4 +56,14 @@ export async function rejectCSR(id: string) {
throw new Error('Network response was not ok')
}
return response.json()
}

export async function revokeCertificate(id: string) {
const response = await fetch("/api/v1/certificate_requests/" + id + "/certificate/reject", {
method: 'post',
})
if (!response.ok) {
throw new Error('Network response was not ok')
}
return response.json()
}

0 comments on commit 42cfe7e

Please sign in to comment.