From b5c05b5f20cc8e39b8ac69632c996e46eb4a07ca Mon Sep 17 00:00:00 2001 From: Bugs5382 <12115015+Bugs5382@users.noreply.github.com> Date: Thu, 15 Oct 2026 15:49:24 -0400 Subject: [PATCH] refactor(console): restyle the first-run wizard and credential request The first-run wizard (token, CA and admin steps) and the "Make a credential request" page move onto the @cryptos-pki/ui kit: Field and Input for every form control, Notice for the fingerprint warnings and errors, FingerprintConfirm for the CA reconfirm step, CopyBlock and KeyBackupStep for the in-browser key flows, and Stepper for the wizard's progress strip. Routes, API calls and refusal wording are unchanged. These were also the last importers of the local components/ui/button and components/ui/input, the local copy-block and key-backup-step. diagnostics-copy (shown on every sign-in gate) picks up the kit's Button too, which was its only remaining local import. With nothing left importing them, components/ui/, copy-block.tsx and key-backup-step.tsx (plus their tests) are removed, closing out #137's design review. Each rebuilt page gets an axe check of its initial state alongside the existing behavioural tests, which stay green unchanged. Refs CryptOS-PKI/cryptos-web#187 Refs CryptOS-PKI/cryptos-web#157 Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com> --- AGENTS.md | 3 +- apps/console/src/components/copy-block.tsx | 66 ------ .../src/components/first-run/admin-step.tsx | 203 ++++++++++++------ .../src/components/first-run/ca-step.tsx | 62 +++--- .../first-run/first-run-wizard.test.tsx | 24 +++ .../components/first-run/first-run-wizard.tsx | 18 +- .../src/components/first-run/token-step.tsx | 46 ++-- .../src/components/key-backup-step.test.tsx | 88 -------- .../src/components/key-backup-step.tsx | 170 --------------- .../components/layout/diagnostics-copy.tsx | 2 +- apps/console/src/components/ui/button.tsx | 65 ------ apps/console/src/components/ui/input.test.tsx | 29 --- apps/console/src/components/ui/input.tsx | 39 ---- apps/console/src/lib/form.ts | 5 +- .../src/pages/credential-request.test.tsx | 6 + apps/console/src/pages/credential-request.tsx | 170 ++++++++++----- 16 files changed, 340 insertions(+), 656 deletions(-) delete mode 100644 apps/console/src/components/copy-block.tsx delete mode 100644 apps/console/src/components/key-backup-step.test.tsx delete mode 100644 apps/console/src/components/key-backup-step.tsx delete mode 100644 apps/console/src/components/ui/button.tsx delete mode 100644 apps/console/src/components/ui/input.test.tsx delete mode 100644 apps/console/src/components/ui/input.tsx diff --git a/AGENTS.md b/AGENTS.md index b556bd1..0c73bae 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,7 +32,8 @@ components, named exports from `src/index.ts`) and The "Make a credential request" page `/request-credential` is outside the gate too, and makes no network calls at all: it must not import the Fleet Manager client. - `src/components/layout/` - app shell: header, sidebar nav, wordmark, theme toggle, auth gate. -- `src/components/ui/` - shadcn/ui primitives (button, card, badge, separator). + There is no local `src/components/ui/`: every primitive comes from `@cryptos-pki/ui`, and + `src/components/` holds only app-specific parts (wizards, dialogs, the topology graph). - `src/context/` - `theme.tsx` (dark/light, persisted) and `auth.tsx` (browser-mTLS gate stub). - `src/lib/` - `mock.ts` (the typed Node model), `mock-fixtures.real.ts`/`mock-fixtures.stub.ts` (the fixture data every mock store's `seed()` copies from, swapped by `vite.config.ts` at build diff --git a/apps/console/src/components/copy-block.tsx b/apps/console/src/components/copy-block.tsx deleted file mode 100644 index 412c316..0000000 --- a/apps/console/src/components/copy-block.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/* -Copyright The CryptOS Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import { Button } from "@/components/ui/button"; -import { downloadText } from "@/lib/download"; - -// CopyBlock shows generated text (a CSR, an OpenSSL section, a command) with a -// copy button and, when a filename is given, a download button. -export const CopyBlock = ({ - filename, - label, - text, -}: { - filename?: string; - label: string; - text: string; -}) => ( -
-
- - {label} - -
- - {filename ? ( - - ) : null} -
-
-
-      {text}
-    
-
-); diff --git a/apps/console/src/components/first-run/admin-step.tsx b/apps/console/src/components/first-run/admin-step.tsx index 76c0101..e11d330 100644 --- a/apps/console/src/components/first-run/admin-step.tsx +++ b/apps/console/src/components/first-run/admin-step.tsx @@ -14,31 +14,83 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { Button } from "@cryptos-pki/ui"; -import { useState } from "react"; +import { + Button, + CopyBlock, + Field, + Input, + inputClasses, + KeyBackupStep, + Notice, +} from "@cryptos-pki/ui"; +import { useEffect, useState } from "react"; -import { CopyBlock } from "@/components/copy-block"; -import { KeyBackupStep } from "@/components/key-backup-step"; import { type FirstAdminResult, submitFirstAdminCertificate } from "@/lib/bootstrap"; import { buildCredentialPkcs12, + generateBackupPassphrase, + keyBackupFilename, + keyBackupPem, keyMatchesCertificate, readCertificateDer, readKeyBackup, } from "@/lib/crypto/key-backup"; -import { downloadBytes, readFileBytes } from "@/lib/download"; +import { generateLeafKeyAndCSR } from "@/lib/crypto/leaf-key"; +import { downloadBytes, downloadText, readFileBytes } from "@/lib/download"; import { fleetErrorMessage } from "@/lib/fleet/error-copy"; -import { fieldClass, labelClass, looksLikeEmail } from "@/lib/form"; +import { looksLikeEmail } from "@/lib/form"; import { credentialFileBase, csrToPem, levelOpensslCommand } from "@/lib/operators"; +const legendClass = "font-mono text-[10.5px] uppercase tracking-[0.08em] text-muted-foreground"; + const SessionCaution = () => ( -

+ The first-run session ends after 15 minutes without a call. If it ends while you are at the CA, start again with the newest token from the log, confirm the CA's fingerprint again, and continue from your key backup. -

+ ); +interface MadeAdminKey { + backupPem: string; + csrDer: Uint8Array; + passphrase: string; +} + +// useMadeAdminKey makes a P-384 admin key and CSR in the browser once PathA +// reaches the backup step (the same pattern CredentialRequestWizard uses for +// its own in-browser key), then makes the encrypted key backup. +const useMadeAdminKey = (active: boolean, email: string) => { + const [made, setMade] = useState(null); + const [error, setError] = useState(null); + + useEffect(() => { + if (!active) return; + let cancelled = false; + void (async () => { + try { + const { csrDer, privateKey } = await generateLeafKeyAndCSR({ + extensionRequest: { level: "admin" }, + sans: [], + subjectCn: email, + }); + const passphrase = generateBackupPassphrase(); + const backupPem = await keyBackupPem(privateKey, passphrase); + if (!cancelled) setMade({ backupPem, csrDer, passphrase }); + } catch (error_: unknown) { + if (!cancelled) + setError(error_ instanceof Error ? error_.message : "Key generation failed"); + } + })(); + return () => { + cancelled = true; + }; + // eslint-disable-next-line react-hooks/exhaustive-deps -- runs once per activation of the step + }, [active]); + + return { error, made }; +}; + const certFrom = (text: string): Uint8Array => readCertificateDer(new TextEncoder().encode(text.trim())); @@ -69,6 +121,7 @@ const PathA = ({ const cleanEmail = email.trim().toLowerCase(); const detailsReady = fullName.trim() !== "" && looksLikeEmail(email); + const { error: keyError, made } = useMadeAdminKey(mode === "make" && !csrDer, cleanEmail); const record = async () => { setPending(true); @@ -116,24 +169,17 @@ const PathA = ({ if (mode === "details") { return (
- - +