rspace-online/src/encryptid/demo.html

673 lines
19 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>EncryptID Demo</title>
<style>
* {
box-sizing: border-box;
}
body {
font-family: system-ui, -apple-system, sans-serif;
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
color: #f1f5f9;
min-height: 100vh;
margin: 0;
padding: 40px 20px;
}
.container {
max-width: 800px;
margin: 0 auto;
}
header {
text-align: center;
margin-bottom: 48px;
}
h1 {
font-size: 2.5rem;
margin: 0 0 8px 0;
background: linear-gradient(135deg, #06b6d4, #22c55e);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.subtitle {
color: #94a3b8;
font-size: 1.125rem;
}
.card {
background: rgba(30, 41, 59, 0.8);
border-radius: 12px;
padding: 24px;
margin-bottom: 24px;
border: 1px solid #334155;
}
.card h2 {
margin: 0 0 16px 0;
font-size: 1.25rem;
display: flex;
align-items: center;
gap: 8px;
}
.demo-section {
margin-bottom: 24px;
}
.demo-section:last-child {
margin-bottom: 0;
}
.button-row {
display: flex;
gap: 12px;
flex-wrap: wrap;
margin-top: 16px;
}
button {
padding: 10px 20px;
border-radius: 8px;
border: none;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.btn-primary {
background: #06b6d4;
color: white;
}
.btn-primary:hover {
background: #0891b2;
}
.btn-secondary {
background: #334155;
color: #f1f5f9;
}
.btn-secondary:hover {
background: #475569;
}
.btn-danger {
background: #ef4444;
color: white;
}
.btn-danger:hover {
background: #dc2626;
}
.status {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
background: #1e293b;
border-radius: 8px;
margin-top: 16px;
}
.status-dot {
width: 12px;
height: 12px;
border-radius: 50%;
flex-shrink: 0;
}
.status-dot.success {
background: #22c55e;
box-shadow: 0 0 8px #22c55e;
}
.status-dot.warning {
background: #eab308;
}
.status-dot.error {
background: #ef4444;
}
.status-dot.neutral {
background: #64748b;
}
.log {
background: #0f172a;
border-radius: 8px;
padding: 16px;
max-height: 200px;
overflow-y: auto;
font-family: monospace;
font-size: 0.75rem;
margin-top: 16px;
}
.log-entry {
margin-bottom: 4px;
color: #94a3b8;
}
.log-entry.success {
color: #22c55e;
}
.log-entry.error {
color: #ef4444;
}
.log-entry.info {
color: #06b6d4;
}
.capabilities {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 12px;
margin-top: 16px;
}
.capability {
display: flex;
align-items: center;
gap: 8px;
padding: 12px;
background: #1e293b;
border-radius: 8px;
font-size: 0.875rem;
}
.capability-icon {
font-size: 1.25rem;
}
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-top: 16px;
}
.feature {
padding: 16px;
background: #1e293b;
border-radius: 8px;
text-align: center;
}
.feature-icon {
font-size: 2rem;
margin-bottom: 8px;
}
.feature-name {
font-weight: 500;
margin-bottom: 4px;
}
.feature-desc {
font-size: 0.75rem;
color: #94a3b8;
}
input[type="text"], input[type="email"] {
padding: 10px 12px;
border-radius: 8px;
border: 1px solid #334155;
background: #1e293b;
color: #f1f5f9;
font-size: 0.875rem;
width: 100%;
max-width: 300px;
}
input:focus {
outline: none;
border-color: #06b6d4;
}
.form-row {
display: flex;
gap: 12px;
align-items: center;
margin-bottom: 12px;
}
label {
font-size: 0.875rem;
color: #94a3b8;
min-width: 100px;
}
.divider {
height: 1px;
background: #334155;
margin: 24px 0;
}
footer {
text-align: center;
margin-top: 48px;
color: #64748b;
font-size: 0.875rem;
}
footer a {
color: #06b6d4;
text-decoration: none;
}
footer a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>🔐 EncryptID</h1>
<p class="subtitle">Unified Identity for the r-Ecosystem</p>
</header>
<!-- Browser Capabilities -->
<div class="card">
<h2>🌐 Browser Capabilities</h2>
<div id="capabilities" class="capabilities">
<div class="capability">
<span class="capability-icon"></span>
<span>Detecting...</span>
</div>
</div>
</div>
<!-- Authentication Demo -->
<div class="card">
<h2>🔑 Authentication</h2>
<div class="demo-section">
<h3 style="margin: 0 0 12px 0; font-size: 1rem; color: #94a3b8;">Register New Passkey</h3>
<div class="form-row">
<label>Username:</label>
<input type="text" id="reg-username" placeholder="your@email.com">
</div>
<div class="form-row">
<label>Display Name:</label>
<input type="text" id="reg-displayname" placeholder="Your Name">
</div>
<button class="btn-primary" id="register-btn">Create Passkey</button>
</div>
<div class="divider"></div>
<div class="demo-section">
<h3 style="margin: 0 0 12px 0; font-size: 1rem; color: #94a3b8;">Sign In</h3>
<button class="btn-primary" id="login-btn">Sign in with Passkey</button>
<div class="status" id="auth-status">
<div class="status-dot neutral"></div>
<div>
<div style="font-weight: 500;">Not authenticated</div>
<div style="font-size: 0.75rem; color: #64748b;">Sign in to access EncryptID features</div>
</div>
</div>
</div>
<div class="log" id="auth-log">
<div class="log-entry info">EncryptID demo loaded</div>
</div>
</div>
<!-- Key Derivation Demo -->
<div class="card">
<h2>🗝️ Derived Keys</h2>
<p style="color: #94a3b8; font-size: 0.875rem; margin-bottom: 16px;">
Keys are derived from your passkey using WebCrypto. They never leave your device.
</p>
<div class="feature-grid">
<div class="feature">
<div class="feature-icon">🔒</div>
<div class="feature-name">Encryption Key</div>
<div class="feature-desc">AES-256-GCM for files & data</div>
</div>
<div class="feature">
<div class="feature-icon">✍️</div>
<div class="feature-name">Signing Key</div>
<div class="feature-desc">ECDSA P-256 for signatures</div>
</div>
<div class="feature">
<div class="feature-icon">🆔</div>
<div class="feature-name">DID Key</div>
<div class="feature-desc">did:key for identity</div>
</div>
</div>
<div class="button-row">
<button class="btn-secondary" id="test-encrypt-btn" disabled>Test Encryption</button>
<button class="btn-secondary" id="test-sign-btn" disabled>Test Signing</button>
<button class="btn-secondary" id="show-did-btn" disabled>Show DID</button>
</div>
<div class="log" id="crypto-log">
<div class="log-entry">Authenticate to enable cryptographic operations</div>
</div>
</div>
<!-- Guardian Setup Demo -->
<div class="card">
<h2>🛡️ Social Recovery</h2>
<p style="color: #94a3b8; font-size: 0.875rem; margin-bottom: 16px;">
Configure guardians to recover your account. No seed phrases needed!
</p>
<!-- The actual web component -->
<encryptid-guardian-setup></encryptid-guardian-setup>
</div>
<!-- Login Button Component Demo -->
<div class="card">
<h2>🎨 Login Button Component</h2>
<p style="color: #94a3b8; font-size: 0.875rem; margin-bottom: 16px;">
Drop-in component for any r-ecosystem app:
</p>
<div style="display: flex; flex-wrap: wrap; gap: 16px; align-items: center;">
<div>
<div style="font-size: 0.75rem; color: #64748b; margin-bottom: 8px;">Small:</div>
<encryptid-login size="small"></encryptid-login>
</div>
<div>
<div style="font-size: 0.75rem; color: #64748b; margin-bottom: 8px;">Medium (default):</div>
<encryptid-login></encryptid-login>
</div>
<div>
<div style="font-size: 0.75rem; color: #64748b; margin-bottom: 8px;">Large:</div>
<encryptid-login size="large"></encryptid-login>
</div>
</div>
<div style="margin-top: 24px;">
<div style="font-size: 0.75rem; color: #64748b; margin-bottom: 8px;">Outline variant:</div>
<encryptid-login variant="outline"></encryptid-login>
</div>
<div style="margin-top: 24px;">
<div style="font-size: 0.75rem; color: #64748b; margin-bottom: 8px;">With user info (when logged in):</div>
<encryptid-login show-user></encryptid-login>
</div>
</div>
<footer>
<p>EncryptID v0.1.0 | <a href="./ENCRYPTID-SPECIFICATION.md">Specification</a></p>
<p>Part of the r-ecosystem: rspace.online | rwallet | rvote | rfiles | rmaps</p>
</footer>
</div>
<!-- Load EncryptID module -->
<script type="module">
import {
detectCapabilities,
registerPasskey,
authenticatePasskey,
getKeyManager,
getSessionManager,
encryptData,
decryptDataAsString,
signData,
verifySignature,
AuthLevel,
// Import UI components to trigger custom element registration
GuardianSetupElement,
EncryptIDLoginButton,
} from '/dist/index.js';
// Force side effects (custom element registration)
console.log('EncryptID components loaded:', GuardianSetupElement.name, EncryptIDLoginButton.name);
// ========================================================================
// CAPABILITY DETECTION
// ========================================================================
async function updateCapabilities() {
const caps = await detectCapabilities();
const container = document.getElementById('capabilities');
container.innerHTML = `
<div class="capability">
<span class="capability-icon">${caps.webauthn ? '✅' : '❌'}</span>
<span>WebAuthn</span>
</div>
<div class="capability">
<span class="capability-icon">${caps.platformAuthenticator ? '✅' : '❌'}</span>
<span>Platform Auth</span>
</div>
<div class="capability">
<span class="capability-icon">${caps.conditionalUI ? '✅' : '⚠️'}</span>
<span>Autofill</span>
</div>
<div class="capability">
<span class="capability-icon">${caps.prfExtension ? '✅' : '⚠️'}</span>
<span>PRF Extension</span>
</div>
`;
}
updateCapabilities();
// ========================================================================
// LOGGING
// ========================================================================
function log(containerId, message, type = 'info') {
const container = document.getElementById(containerId);
const entry = document.createElement('div');
entry.className = `log-entry ${type}`;
entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
container.appendChild(entry);
container.scrollTop = container.scrollHeight;
}
// ========================================================================
// AUTH STATUS UPDATE
// ========================================================================
function updateAuthStatus() {
const session = getSessionManager();
const statusEl = document.getElementById('auth-status');
const did = session.getDID();
const level = session.getAuthLevel();
const isValid = session.isValid();
if (isValid && did) {
statusEl.innerHTML = `
<div class="status-dot success"></div>
<div>
<div style="font-weight: 500;">Authenticated</div>
<div style="font-size: 0.75rem; color: #64748b;">
${did.slice(0, 30)}...
</div>
<div style="font-size: 0.625rem; color: #22c55e; margin-top: 4px;">
Auth Level: ${AuthLevel[level]}
</div>
</div>
`;
// Enable crypto buttons
document.getElementById('test-encrypt-btn').disabled = false;
document.getElementById('test-sign-btn').disabled = false;
document.getElementById('show-did-btn').disabled = false;
} else {
statusEl.innerHTML = `
<div class="status-dot neutral"></div>
<div>
<div style="font-weight: 500;">Not authenticated</div>
<div style="font-size: 0.75rem; color: #64748b;">Sign in to access EncryptID features</div>
</div>
`;
// Disable crypto buttons
document.getElementById('test-encrypt-btn').disabled = true;
document.getElementById('test-sign-btn').disabled = true;
document.getElementById('show-did-btn').disabled = true;
}
}
// Initial status check
updateAuthStatus();
// ========================================================================
// REGISTRATION
// ========================================================================
document.getElementById('register-btn').addEventListener('click', async () => {
const username = document.getElementById('reg-username').value.trim();
const displayName = document.getElementById('reg-displayname').value.trim();
if (!username) {
log('auth-log', 'Please enter a username', 'error');
return;
}
log('auth-log', 'Starting passkey registration...', 'info');
try {
const credential = await registerPasskey(
username,
displayName || username
);
log('auth-log', `Passkey created! ID: ${credential.credentialId.slice(0, 20)}...`, 'success');
log('auth-log', `PRF supported: ${credential.prfSupported}`, 'info');
} catch (error) {
log('auth-log', `Registration failed: ${error.message}`, 'error');
}
});
// ========================================================================
// LOGIN
// ========================================================================
document.getElementById('login-btn').addEventListener('click', async () => {
log('auth-log', 'Starting authentication...', 'info');
try {
const result = await authenticatePasskey();
log('auth-log', `Authentication successful!`, 'success');
log('auth-log', `Credential: ${result.credentialId.slice(0, 20)}...`, 'info');
log('auth-log', `PRF available: ${!!result.prfOutput}`, 'info');
// Initialize key manager
const keyManager = getKeyManager();
if (result.prfOutput) {
await keyManager.initFromPRF(result.prfOutput);
log('auth-log', 'Keys derived from PRF output', 'success');
} else {
// Would prompt for passphrase fallback in real app
log('auth-log', 'PRF not available - would use passphrase fallback', 'warning');
}
// Get derived keys
const keys = await keyManager.getKeys();
log('auth-log', `DID: ${keys.did.slice(0, 40)}...`, 'info');
// Create session
const session = getSessionManager();
await session.createSession(result, keys.did, {
encrypt: true,
sign: true,
wallet: false,
});
log('auth-log', 'Session created', 'success');
updateAuthStatus();
} catch (error) {
log('auth-log', `Authentication failed: ${error.message}`, 'error');
}
});
// ========================================================================
// CRYPTO TESTS
// ========================================================================
document.getElementById('test-encrypt-btn').addEventListener('click', async () => {
const keyManager = getKeyManager();
const keys = await keyManager.getKeys();
const testMessage = 'Hello, EncryptID! 🔐';
log('crypto-log', `Encrypting: "${testMessage}"`, 'info');
const encrypted = await encryptData(keys.encryptionKey, testMessage);
log('crypto-log', `Encrypted (${encrypted.ciphertext.byteLength} bytes)`, 'success');
const decrypted = await decryptDataAsString(keys.encryptionKey, encrypted);
log('crypto-log', `Decrypted: "${decrypted}"`, 'success');
log('crypto-log', `Match: ${decrypted === testMessage}`, decrypted === testMessage ? 'success' : 'error');
});
document.getElementById('test-sign-btn').addEventListener('click', async () => {
const keyManager = getKeyManager();
const keys = await keyManager.getKeys();
const testData = 'Sign this message';
log('crypto-log', `Signing: "${testData}"`, 'info');
const signed = await signData(keys.signingKeyPair, testData);
log('crypto-log', `Signature (${signed.signature.byteLength} bytes)`, 'success');
const valid = await verifySignature(signed);
log('crypto-log', `Signature valid: ${valid}`, valid ? 'success' : 'error');
});
document.getElementById('show-did-btn').addEventListener('click', async () => {
const keyManager = getKeyManager();
const keys = await keyManager.getKeys();
log('crypto-log', `Your DID:`, 'info');
log('crypto-log', keys.did, 'success');
});
// ========================================================================
// LOGIN BUTTON EVENTS
// ========================================================================
document.querySelectorAll('encryptid-login').forEach(button => {
button.addEventListener('login-success', (e) => {
log('auth-log', `Login button: Success! DID: ${e.detail.did.slice(0, 30)}...`, 'success');
updateAuthStatus();
});
button.addEventListener('login-error', (e) => {
log('auth-log', `Login button: Error - ${e.detail.error}`, 'error');
});
button.addEventListener('logout', () => {
log('auth-log', 'Logged out', 'info');
updateAuthStatus();
});
});
// ========================================================================
// GUARDIAN EVENTS
// ========================================================================
document.querySelector('encryptid-guardian-setup').addEventListener('guardian-added', (e) => {
log('auth-log', `Guardian added: ${e.detail.name}`, 'success');
});
</script>
</body>
</html>