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:
@@ -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'),
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user