import React, { useState } from 'react'; import { Shield, Zap, AlertCircle, Loader2, CheckCircle, Fingerprint, Key } from 'lucide-react'; interface RegisterProps { onRegisterSuccess: (token: string, masterKey: Uint8Array, communityId: string, username: string, userId: string, role?: string) => void; onSwitchToLogin: () => void; } export const Register: React.FC = ({ onRegisterSuccess, onSwitchToLogin }) => { const [username, setUsername] = useState(''); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [communityName, setCommunityName] = useState(''); const [authMethod, setAuthMethod] = useState<'password' | 'passkey'>('password'); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [passwordStrength, setPasswordStrength] = useState<{score: number; message: string} | null>(null); const checkPasswordStrength = (pwd: string) => { if (pwd.length < 12) { setPasswordStrength({score: 0, message: 'Too short (min 12 characters)'}); return; } let score = 0; if (/[A-Z]/.test(pwd)) score++; if (/[a-z]/.test(pwd)) score++; if (/[0-9]/.test(pwd)) score++; if (/[^A-Za-z0-9]/.test(pwd)) score++; if (pwd.length >= 16) score++; const messages = ['Weak', 'Fair', 'Good', 'Strong', 'Very Strong']; setPasswordStrength({score, message: messages[Math.min(score - 1, 4)]}); }; const handlePasswordRegister = async (e: React.FormEvent) => { e.preventDefault(); setError(null); if (password !== confirmPassword) { setError('Passwords do not match'); return; } if (!passwordStrength || passwordStrength.score < 3) { setError('Please use a stronger password'); return; } setIsLoading(true); try { const res = await fetch('/api/auth/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, email, password, communityName: communityName || username + "'s Community", }), }); if (!res.ok) { const data = await res.json(); throw new Error(data.error || 'Registration failed'); } const data = await res.json(); const masterKeyBytes = new TextEncoder().encode(data.masterKey); localStorage.setItem('auth_token', data.token); onRegisterSuccess(data.token, masterKeyBytes, data.communityId, data.username, data.userId, data.role); } catch (err) { setError((err as Error).message); } finally { setIsLoading(false); } }; const handlePasskeyRegister = async (e: React.FormEvent) => { e.preventDefault(); setError(null); setIsLoading(true); try { // Step 1: Begin registration const beginRes = await fetch('/api/auth/register/passkey/begin', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, displayName: username, email, }), }); if (!beginRes.ok) throw new Error('Failed to start passkey registration'); const options = await beginRes.json(); // Step 2: Create credential via WebAuthn const credential = await navigator.credentials.create({ publicKey: { challenge: base64urlToBuffer(options.challenge), rp: options.rp, user: { id: new TextEncoder().encode(options.user.id), name: options.user.name, displayName: options.user.displayName, }, pubKeyCredParams: options.pubKeyCredParams, timeout: options.timeout, attestation: options.attestation as AttestationConveyancePreference, authenticatorSelection: options.authenticatorSelection as AuthenticatorSelectionCriteria, }, }) as PublicKeyCredential | null; if (!credential) throw new Error('Passkey creation cancelled'); const response = credential.response as AuthenticatorAttestationResponse; // Step 3: Finish registration const finishRes = await fetch('/api/auth/register/passkey/finish', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: credential.id, rawId: bufferToBase64url(credential.rawId), type: credential.type, response: { attestationObject: bufferToBase64url(response.attestationObject), clientDataJSON: bufferToBase64url(response.clientDataJSON), }, }), }); if (!finishRes.ok) throw new Error('Failed to complete passkey registration'); const data = await finishRes.json(); const masterKeyBytes = new TextEncoder().encode(data.masterKey || 'this-is-a-32-byte-master-key-xyz'); localStorage.setItem('auth_token', data.token); onRegisterSuccess(data.token, masterKeyBytes, data.communityId, username, data.userId ?? 'user-passkey', data.role); } catch (err) { setError((err as Error).message); } finally { setIsLoading(false); } }; const base64urlToBuffer = (base64url: string): ArrayBuffer => { const base64 = base64url.replace(/-/g, '+').replace(/_/g, '/'); const padded = base64.padEnd(base64.length + (4 - (base64.length % 4)) % 4, '='); const binary = atob(padded); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) { bytes[i] = binary.charCodeAt(i); } return bytes.buffer; }; const bufferToBase64url = (buffer: ArrayBuffer): string => { const bytes = new Uint8Array(buffer); let binary = ''; for (let i = 0; i < bytes.length; i++) { binary += String.fromCharCode(bytes[i]); } return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); }; return (
{/* Background Effects */}
{/* Logo */}

ArmaCloud

Create your account

{/* Auth Method Selector */}
{/* Register Card */}
{/* Common Fields */}
setUsername(e.target.value)} placeholder="Operator ID" className="w-full px-4 py-3 bg-background border border-border/50 rounded-xl text-foreground placeholder:text-muted-foreground/30 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all" required disabled={isLoading} />
setEmail(e.target.value)} placeholder="operator@backbone.net" className="w-full px-4 py-3 bg-background border border-border/50 rounded-xl text-foreground placeholder:text-muted-foreground/30 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all" disabled={isLoading} />
{/* Password-specific fields */} {authMethod === 'password' && ( <>
{ setPassword(e.target.value); checkPasswordStrength(e.target.value); }} placeholder="••••••••••••" className="w-full px-4 py-3 bg-background border border-border/50 rounded-xl text-foreground placeholder:text-muted-foreground/30 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all" required disabled={isLoading} /> {passwordStrength && password.length > 0 && (
{passwordStrength.message}
)}
setConfirmPassword(e.target.value)} placeholder="••••••••••••" className="w-full px-4 py-3 bg-background border border-border/50 rounded-xl text-foreground placeholder:text-muted-foreground/30 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all" required disabled={isLoading} />
)}
setCommunityName(e.target.value)} placeholder="e.g., Tactical Command Alpha" className="w-full px-4 py-3 bg-background border border-border/50 rounded-xl text-foreground placeholder:text-muted-foreground/30 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all" disabled={isLoading} />
{error && (

Registration failed

{error}

)}
{/* Security Info */}

Your data is end-to-end encrypted. Encryption keys never leave your device.

{/* Switch to Login */}

Already have an account?{' '}

); };