feat: setup wizard key generation

added ed25519 keypair generation to setup endpoint
wired route for POST /api/setup/generate-keys

existing registration token system handles deployment
This commit is contained in:
Fimeg
2025-11-02 09:47:16 -05:00
parent 822f57bbdc
commit 0062e2acab
4 changed files with 103 additions and 107 deletions

View File

@@ -74,7 +74,7 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
name: 'Settings',
href: '/settings',
icon: Settings,
current: location.pathname === '/settings',
current: location.pathname.startsWith('/settings'),
},
];

View File

@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Settings, Database, User, Shield, Eye, EyeOff, CheckCircle } from 'lucide-react';
import { Settings, Database, User, Shield, Eye, EyeOff, CheckCircle, Key } from 'lucide-react';
import { toast } from 'react-hot-toast';
import { setupApi } from '@/lib/api';
import { useAuthStore } from '@/lib/store';
@@ -18,15 +18,24 @@ interface SetupFormData {
maxSeats: string;
}
interface SigningKeys {
public_key: string;
private_key: string;
fingerprint: string;
algorithm: string;
}
const Setup: React.FC = () => {
const navigate = useNavigate();
const { logout } = useAuthStore();
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [envContent, setEnvContent] = useState<string | null>(null);
const [envContent, setEnvContent] = useState<string | null>(null);
const [showSuccess, setShowSuccess] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const [showDbPassword, setShowDbPassword] = useState(false);
const [signingKeys, setSigningKeys] = useState<SigningKeys | null>(null);
const [generatingKeys, setGeneratingKeys] = useState(false);
const [formData, setFormData] = useState<SetupFormData>({
adminUser: 'admin',
@@ -49,6 +58,27 @@ const Setup: React.FC = () => {
}));
};
const handleGenerateKeys = async () => {
setGeneratingKeys(true);
try {
const response = await fetch('/api/setup/generate-keys', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
});
if (!response.ok) {
throw new Error('Failed to generate keys');
}
const keys: SigningKeys = await response.json();
setSigningKeys(keys);
toast.success('Signing keys generated successfully!');
} catch (error: any) {
toast.error(error.message || 'Failed to generate keys');
setError('Failed to generate signing keys');
} finally {
setGeneratingKeys(false);
}
};
const validateForm = (): boolean => {
if (!formData.adminUser.trim()) {
setError('Admin username is required');
@@ -114,7 +144,13 @@ const Setup: React.FC = () => {
try {
const result = await setupApi.configure(formData);
setEnvContent(result.envContent || null);
// Add signing keys to env content if generated
let finalEnvContent = result.envContent || '';
if (signingKeys && finalEnvContent) {
finalEnvContent += `\n# Ed25519 Signing Keys (for agent updates)\nREDFLAG_SIGNING_PRIVATE_KEY=${signingKeys.private_key}\n`;
}
setEnvContent(finalEnvContent || null);
setShowSuccess(true);
toast.success(result.message || 'Configuration saved successfully!');
@@ -322,6 +358,69 @@ const Setup: React.FC = () => {
</div>
</div>
{/* Security Keys Section */}
<div>
<div className="flex items-center mb-4">
<Key className="h-5 w-5 text-indigo-600 mr-2" />
<h3 className="text-lg font-semibold text-gray-900">Security Keys</h3>
</div>
<div className="bg-blue-50 border border-blue-200 rounded-md p-4 mb-4">
<p className="text-sm text-blue-800">
Generate Ed25519 signing keys for secure agent updates.
<strong> Save the private key securely</strong> - it will be included in your configuration.
</p>
</div>
{!signingKeys ? (
<button
type="button"
onClick={handleGenerateKeys}
disabled={generatingKeys}
className="w-full py-2 px-4 border border-indigo-600 text-indigo-600 rounded-md hover:bg-indigo-50 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center"
>
{generatingKeys ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-indigo-600 mr-2"></div>
Generating Keys...
</>
) : (
<>
<Key className="h-4 w-4 mr-2" />
Generate Signing Keys
</>
)}
</button>
) : (
<div className="space-y-3">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Public Key Fingerprint
</label>
<input
readOnly
value={signingKeys.fingerprint}
className="block w-full px-3 py-2 bg-gray-100 border border-gray-300 rounded-md font-mono text-sm"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Algorithm
</label>
<input
readOnly
value={signingKeys.algorithm.toUpperCase()}
className="block w-full px-3 py-2 bg-gray-100 border border-gray-300 rounded-md text-sm"
/>
</div>
<div className="bg-green-50 border border-green-200 rounded-md p-3">
<p className="text-sm text-green-800">
✓ Keys generated! Private key will be securely included in your configuration file.
</p>
</div>
</div>
)}
</div>
{/* Database Configuration */}
<div>
<div className="flex items-center mb-4">