Skip to content

Commit

Permalink
feat 🦄: Implemented the fronted managing of the mutual tls certificat…
Browse files Browse the repository at this point in the history
…e managed on the canister.
  • Loading branch information
lorenzoronzani committed May 22, 2024
1 parent d8c3a70 commit 4d4e4b5
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 42 deletions.
71 changes: 41 additions & 30 deletions frontend/app/dashboard/new-deployment/page.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
"use client";

import { BackButton } from "@/components/back-button";
import { useToast } from "@/components/ui/use-toast";
import { useDeploymentContext } from "@/contexts/DeploymentContext";
import {BackButton} from "@/components/back-button";
import {useToast} from "@/components/ui/use-toast";
import {useDeploymentContext} from "@/contexts/DeploymentContext";
import {
type OnWsErrorCallback,
type OnWsMessageCallback,
type OnWsOpenCallback,
useIcContext,
type OnWsErrorCallback,
} from "@/contexts/IcContext";
import type { DeploymentParams, DeploymentState } from "@/declarations/backend.did";
import { extractDeploymentCreated } from "@/helpers/deployment";
import { extractOk } from "@/helpers/result";
import { sendManifestToProvider } from "@/services/deployment";
import { useRouter } from "next/navigation";
import { useCallback, useEffect, useMemo, useState } from "react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { AlertCircle } from "lucide-react";
import { displayE8sAsIcp, icpToE8s } from "@/helpers/ui";
import { transferE8sToBackend } from "@/services/backend";
import { Spinner } from "@/components/spinner";
import { NewDeploymentForm } from "@/components/new-deployment-form";
import type {DeploymentParams, DeploymentState} from "@/declarations/backend.did";
import {extractDeploymentCreated} from "@/helpers/deployment";
import {extractOk} from "@/helpers/result";
import {sendManifestToProvider} from "@/services/deployment";
import {useRouter} from "next/navigation";
import {useCallback, useEffect, useMemo, useState} from "react";
import {Alert, AlertDescription, AlertTitle} from "@/components/ui/alert";
import {AlertCircle} from "lucide-react";
import {displayE8sAsIcp, icpToE8s} from "@/helpers/ui";
import {transferE8sToBackend} from "@/services/backend";
import {Spinner} from "@/components/spinner";
import {NewDeploymentForm} from "@/components/new-deployment-form";

const FETCH_DEPLOYMENT_PRICE_INTERVAL_MS = 30_000; // 30 seconds

export default function NewDeployment() {
const router = useRouter();
const { backendActor, openWs, closeWs, setWsCallbacks, ledgerCanister, ledgerData, refreshLedgerData } = useIcContext();
const { tlsCertificateData, loadOrCreateCertificate, fetchDeployments } =
const {backendActor, openWs, closeWs, setWsCallbacks, ledgerCanister, ledgerData, refreshLedgerData} = useIcContext();
const {tlsCertificateData, loadOrCreateCertificate, fetchDeployments} =
useDeploymentContext();
const [isSubmitting, setIsSubmitting] = useState(false);
const [isDeploying, setIsDeploying] = useState(false);
Expand All @@ -38,12 +38,13 @@ export default function NewDeployment() {
const [deploymentE8sPrice, setDeploymentE8sPrice] = useState<bigint | null>(null);
const [fetchDeploymentPriceInterval, setFetchDeploymentPriceInterval] = useState<NodeJS.Timeout | null>(null);
const userHasEnoughBalance = useMemo(() =>
ledgerData.balanceE8s !== null && deploymentE8sPrice !== null && ledgerData.balanceE8s > deploymentE8sPrice,
ledgerData.balanceE8s !== null && deploymentE8sPrice !== null && ledgerData.balanceE8s > deploymentE8sPrice,
[ledgerData.balanceE8s, deploymentE8sPrice]
);
const [certificateStatus, setCertificateStatus] = useState<string | null>(null);
const [paymentStatus, setPaymentStatus] = useState<string | null>(null);
const [deploymentParams, setDeploymentParams] = useState<DeploymentParams | null>(null);
const { toast } = useToast();
const {toast} = useToast();

const toastError = useCallback(
(message: string) => {
Expand Down Expand Up @@ -77,7 +78,7 @@ export default function NewDeployment() {

console.log("deployment id", deploymentId);

setDeploymentSteps([{ Initialized: null }]);
setDeploymentSteps([{Initialized: null}]);
} catch (e) {
console.error("Failed to create deployment:", e);
setDeploymentError("Failed to create deployment, see console for details");
Expand Down Expand Up @@ -106,12 +107,12 @@ export default function NewDeployment() {

try {
if ("LeaseCreated" in deploymentUpdate.update) {
const { manifest_sorted_json, dseq } = extractDeploymentCreated(
const {manifest_sorted_json, dseq} = extractDeploymentCreated(
deploymentSteps.find((el) =>
el.hasOwnProperty("DeploymentCreated")
)!
);
const { provider_url } = deploymentUpdate.update.LeaseCreated;
const {provider_url} = deploymentUpdate.update.LeaseCreated;

const manifestUrl = new URL(
`/deployment/${dseq}/manifest`,
Expand Down Expand Up @@ -220,13 +221,16 @@ export default function NewDeployment() {
setDeploymentParams(values);
setIsDeploying(false);
setIsSubmitting(true);
setCertificateStatus(null);
setPaymentStatus(null);

try {
setCertificateStatus("Retrieving certificate...");
const cert = await loadOrCreateCertificate(backendActor!);
if (!cert) {
throw new Error("No certificate");
}
setCertificateStatus("Certificate retrieved");

setPaymentStatus(`Sending ~${displayE8sAsIcp(deploymentE8sPrice)} to backend canister...`);

Expand Down Expand Up @@ -323,7 +327,7 @@ export default function NewDeployment() {
return (
<div className="flex-1 space-y-4 p-8 pt-6">
<div className="flex items-center justify-start">
<BackButton />
<BackButton/>
<h2 className="ml-4 text-3xl font-bold tracking-tight">
Create Deployment
</h2>
Expand All @@ -342,13 +346,13 @@ export default function NewDeployment() {
Price (est.):
</h5>
{deploymentE8sPrice !== null ? (
<pre>~{displayE8sAsIcp(deploymentE8sPrice, { maximumFractionDigits: 6 })}</pre>
<pre>~{displayE8sAsIcp(deploymentE8sPrice, {maximumFractionDigits: 6})}</pre>
) : (
<Spinner />
<Spinner/>
)}
{(!(isSubmitting || isDeploying) && (deploymentE8sPrice !== null) && !userHasEnoughBalance) && (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertCircle className="h-4 w-4"/>
<AlertTitle>Insufficient balance</AlertTitle>
<AlertDescription>
<p>Please top up your account.</p>
Expand All @@ -358,11 +362,18 @@ export default function NewDeployment() {
>
{ledgerData.accountId?.toHex()}
</pre>
<p className="mt-2">If you&apos;ve already topped up your account, please refresh the balance on the top bar.</p>
<p className="mt-2">If you&apos;ve already topped up your account, please refresh the balance on the
top bar.</p>
</AlertDescription>
</Alert>
)}
</div>
{certificateStatus && (
<div className="flex flex-col gap-3">
<h5 className="font-bold">Certificate Status:</h5>
<p>{certificateStatus}</p>
</div>
)}
{paymentStatus && (
<div className="flex flex-col gap-3">
<h5 className="font-bold">Payment Status:</h5>
Expand All @@ -380,13 +391,13 @@ export default function NewDeployment() {
{idx + 1}. {el}
</p>
))}
{isDeploying && <Spinner />}
{isDeploying && <Spinner/>}
</div>
</div>
)}
{Boolean(deploymentError) && (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertCircle className="h-4 w-4"/>
<AlertTitle>Deployment Error</AlertTitle>
<AlertDescription>
<p>{deploymentError}</p>
Expand Down
24 changes: 15 additions & 9 deletions frontend/contexts/DeploymentContext.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { type _SERVICE, type GetDeploymentsResult } from "@/declarations/backend.did";
import { getDeploymentCreatedDate } from "@/helpers/deployment";
import { type OkType, extractOk } from "@/helpers/result";
import { X509CertificateData, createX509, loadCertificate, saveCertificate } from "@/lib/certificate";
import { type BackendActor } from "@/services/backend";
import { createContext, useCallback, useContext, useState } from "react";
import {type GetDeploymentsResult} from "@/declarations/backend.did";
import {getDeploymentCreatedDate} from "@/helpers/deployment";
import {extractOk, type OkType} from "@/helpers/result";
import {createX509, X509CertificateData} from "@/lib/certificate";
import {type BackendActor} from "@/services/backend";
import {createContext, useCallback, useContext, useState} from "react";
import {getCurrentUser, setUserMutualTlsCertificate} from "@/services/user";

export type Deployments = OkType<GetDeploymentsResult>;

Expand All @@ -20,7 +21,7 @@ type DeploymentProviderProps = {
children?: React.ReactNode;
}

export const DeploymentProvider: React.FC<DeploymentProviderProps> = ({ children }) => {
export const DeploymentProvider: React.FC<DeploymentProviderProps> = ({children}) => {
const [tlsCertificateData, setCertificateData] = useState<X509CertificateData | null>(null);
const [deployments, setDeployments] = useState<Deployments>([]);

Expand All @@ -38,7 +39,12 @@ export const DeploymentProvider: React.FC<DeploymentProviderProps> = ({ children
}, []);

const loadOrCreateCertificate = useCallback(async (actor: BackendActor): Promise<X509CertificateData | null> => {
let certData = loadCertificate();
const mutualTlsCertificateStringified = (await getCurrentUser(actor)).mutual_tls_certificate;
let certData = null;
if (mutualTlsCertificateStringified) {
certData = JSON.parse(mutualTlsCertificateStringified);
}

if (!certData) {
try {
const akashAddress = extractOk(await actor.address());
Expand All @@ -48,7 +54,7 @@ export const DeploymentProvider: React.FC<DeploymentProviderProps> = ({ children
Buffer.from(certData.pubKey, "utf-8").toString("base64"),
));

saveCertificate(certData);
await setUserMutualTlsCertificate(actor, JSON.stringify(certData));
} catch (e) {
console.error(e);
alert("Failed to create certificate, see console for details");
Expand Down
10 changes: 7 additions & 3 deletions frontend/services/user.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type User } from "@/declarations/backend.did";
import { extractOk } from "@/helpers/result";
import { BackendActor } from "./backend";
import {type User} from "@/declarations/backend.did";
import {extractOk} from "@/helpers/result";
import {BackendActor} from "./backend";

export const getCurrentUser = async (actor: BackendActor): Promise<User> => {
const res = await actor.get_my_user();
Expand Down Expand Up @@ -28,3 +28,7 @@ export const getOrCreateCurrentUser = async (actor: BackendActor): Promise<User>

return user;
};

export const setUserMutualTlsCertificate = async (actor: BackendActor, stringifiedCertificate: string): Promise<void> => {
extractOk(await actor.set_mutual_tls_certificate(stringifiedCertificate));
}

0 comments on commit 4d4e4b5

Please sign in to comment.