673 lines
19 KiB
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>
|