Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: logging in to GoCert #44

Merged
merged 13 commits into from
Jul 24, 2024
82 changes: 77 additions & 5 deletions ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
"test-live": "vitest"
},
"dependencies": {
"jwt-decode": "^4.0.0",
"next": "14.2.3",
"pkijs": "^3.1.0",
"react": "^18",
"react-cookie": "^7.1.4",
"react-dom": "^18",
"react-query": "^3.39.3",
"sass": "^1.77.4",
Expand Down
85 changes: 85 additions & 0 deletions ui/src/app/aside.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { SetStateAction, Dispatch, useState, createContext, ChangeEvent } from "react"
import { useMutation, useQueryClient } from "react-query";
import { postCSR } from "./queries";
import { extractCSR } from "./utils";
import { useCookies } from "react-cookie";

type AsideContextType = {
isOpen: boolean,
setIsOpen: Dispatch<SetStateAction<boolean>>
}
export const AsideContext = createContext<AsideContextType>({ isOpen: false, setIsOpen: () => { } });

export function Aside({ isOpen, setIsOpen }: { isOpen: boolean, setIsOpen: Dispatch<SetStateAction<boolean>> }) {
const [cookies, setCookie, removeCookie] = useCookies(['user_token']);
const queryClient = useQueryClient()
const mutation = useMutation(postCSR, {
onSuccess: () => {
queryClient.invalidateQueries('csrs')
},
})
const [CSRPEMString, setCSRPEMString] = useState<string>("")
const handleTextChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
setCSRPEMString(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) {
setCSRPEMString(e.target.result.toString());
}
}
};
reader.readAsText(file);
}
};
return (
<aside className={"l-aside" + (isOpen ? "" : " is-collapsed")} id="aside-panel" aria-label="aside-panel" >
<div className="p-panel">
<div className="p-panel__header">
<h4 className="p-panel__title">Add a New Certificate Request</h4>
<div className="p-panel__controls">
<button onClick={() => setIsOpen(false)} className="p-button--base u-no-margin--bottom has-icon"><i className="p-icon--close"></i></button>
</div>
</div>
<div className="p-panel__content">
<form className="p-form p-form--stacked">
<div className="p-form__group row">
<label htmlFor="textarea">
Enter or upload the CSR in PEM format below
</label>
<textarea id="csr-textarea" name="textarea" rows={10} placeholder="-----BEGIN CERTIFICATE REQUEST-----" onChange={handleTextChange} value={CSRPEMString} />
</div>
<div className="p-form__group row">
<input type="file" name="upload" accept=".pem,.csr" onChange={handleFileChange}></input>
</div>
<div className="p-form__group row">
<SubmitCSR csrText={CSRPEMString} onClickFunc={() => mutation.mutate({ authToken: cookies.user_token, csr: CSRPEMString })} />
</div>
</form>
</div>
</div >
</aside >
)
}

function SubmitCSR({ csrText, onClickFunc }: { csrText: string, onClickFunc: any }) {
let csrIsValid = false
try {
extractCSR(csrText.trim())
csrIsValid = true
}
catch { }

const validationComponent = csrText == "" ? <></> : csrIsValid ? <div><i className="p-icon--success"></i>Valid CSR</div> : <div><i className="p-icon--error"></i>Invalid CSR</div>
const buttonComponent = csrIsValid ? <button className="p-button--positive u-float-right" name="submit" onClick={onClickFunc} >Submit</button> : <button className="p-button--positive u-float-right" name="submit" disabled={true} onClick={onClickFunc} >Submit</button>
return (
<>
{validationComponent}
{buttonComponent}
</>
)
}
38 changes: 38 additions & 0 deletions ui/src/app/auth/authContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"use client"

import { createContext, useContext, useState, useEffect } from 'react';
import { User } from '../types';
import { useCookies } from 'react-cookie';
import { jwtDecode } from 'jwt-decode';
import { useRouter } from 'next/navigation';

type AuthContextType = {
user: User | null
}

const AuthContext = createContext<AuthContextType>({ user: null });

export const AuthProvider = ({ children }: Readonly<{ children: React.ReactNode }>) => {
const [cookies, setCookie, removeCookie] = useCookies(['user_token']);
const [user, setUser] = useState<User | null>(null);
const router = useRouter();

useEffect(() => {
const token = cookies.user_token;
if (token) {
let userObject = jwtDecode(cookies.user_token) as User
setUser(userObject);
} else {
setUser(null)
router.push('/login');
}
}, [cookies.user_token, router]);

return (
<AuthContext.Provider value={{ user }}>
{children}
</AuthContext.Provider>
);
};

export const useAuth = () => useContext(AuthContext);
8 changes: 5 additions & 3 deletions ui/src/app/certificate_requests/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useMutation, useQueryClient } from "react-query"
import { ConfirmationModalData } from "./row"
import { extractCert, csrMatchesCertificate } from "../utils"
import { postCertToID } from "../queries"
import { useCookies } from "react-cookie"

interface ConfirmationModalProps {
modalData: ConfirmationModalData
Expand All @@ -12,7 +13,7 @@ interface ConfirmationModalProps {

export function ConfirmationModal({ modalData, setModalData }: ConfirmationModalProps) {
const confirmQuery = () => {
modalData?.func()
modalData?.onMouseDownFunc()
setModalData(null)
}
return (
Expand Down Expand Up @@ -68,8 +69,9 @@ interface SubmitCertificateModalProps {
setFormOpen: Dispatch<SetStateAction<boolean>>
}
export function SubmitCertificateModal({ id, csr, cert, setFormOpen }: SubmitCertificateModalProps) {
const [cookies, setCookie, removeCookie] = useCookies(['user_token']);
const queryClient = useQueryClient()
const mutation = useMutation(postCertToID(id), {
const mutation = useMutation(postCertToID, {
onSuccess: () => {
queryClient.invalidateQueries('csrs')
},
Expand All @@ -93,7 +95,7 @@ export function SubmitCertificateModal({ id, csr, cert, setFormOpen }: SubmitCer
}
};
const handleSubmit = () => {
mutation.mutate(certificatePEMString)
mutation.mutate({ id: id, authToken: cookies.user_token, cert: certificatePEMString })
setFormOpen(false)
}
return (
Expand Down
25 changes: 23 additions & 2 deletions ui/src/app/certificate_requests/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { useQuery } from "react-query"
import { CertificateRequestsTable } from "./table"
import { getCertificateRequests } from "../queries"
import { CSREntry } from "../types"
import { useCookies } from "react-cookie"
import { useRouter } from "next/navigation"

function Error({ msg }: { msg: string }) {
return (
Expand Down Expand Up @@ -35,9 +37,28 @@ function Loading() {
}

export default function CertificateRequests() {
const query = useQuery<CSREntry[], Error>('csrs', getCertificateRequests)
const router = useRouter()
const [cookies, setCookie, removeCookie] = useCookies(['user_token']);
if (!cookies.user_token) {
router.push("/login")
}
const query = useQuery<CSREntry[], Error>({
queryKey: ['csrs', cookies.user_token],
queryFn: () => getCertificateRequests({ authToken: cookies.user_token }),
retry: (failureCount, error): boolean => {
if (error.message.includes("401")) {
return false
}
return true
},
})
if (query.status == "loading") { return <Loading /> }
if (query.status == "error") { return <Error msg={query.error.message} /> }
if (query.status == "error") {
if (query.error.message.includes("401")) {
removeCookie("user_token")
}
return <Error msg={query.error.message} />
}
const csrs = Array.from(query.data ? query.data : [])
return (
<CertificateRequestsTable csrs={csrs} />
Expand Down
Loading
Loading