Mostly working, saving.
This commit is contained in:
185
static/reset.html
Normal file
185
static/reset.html
Normal file
@@ -0,0 +1,185 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Add Device - Passkey Authentication</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<script src="https://unpkg.com/@simplewebauthn/browser/dist/bundle/index.umd.min.js"></script>
|
||||
<script src="/static/qrcodejs/qrcode.min.js"></script>
|
||||
<script src="/static/awaitable-websocket.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Request Reset View -->
|
||||
<div id="requestView" class="view">
|
||||
<h1>🔓 Add Device</h1>
|
||||
<p>This page is for adding a new device to an existing account. You need a device addition link to proceed.</p>
|
||||
<div id="requestStatus" class="status info" style="display: block;">
|
||||
<strong>How to get a device addition link:</strong><br>
|
||||
1. Log into your account on a device you already have<br>
|
||||
2. Click "Generate Device Link" in your dashboard<br>
|
||||
3. Copy the link or scan the QR code to add this device
|
||||
</div>
|
||||
<p class="toggle-link" onclick="window.location.href='/'">
|
||||
Back to Login
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Add Passkey View -->
|
||||
<div id="addPasskeyView" class="view">
|
||||
<h1>🔑 Add New Passkey</h1>
|
||||
<div id="userInfo" class="token-info">
|
||||
<p><strong>Account:</strong> <span id="userName"></span></p>
|
||||
<p><small>You are about to add a new passkey to this account.</small></p>
|
||||
</div>
|
||||
<div id="addPasskeyStatus" class="status"></div>
|
||||
<button id="addPasskeyBtn" class="btn-primary">Add New Passkey</button>
|
||||
<p class="toggle-link" onclick="window.location.href='/'">
|
||||
Back to Login
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Success Complete View -->
|
||||
<div id="completeView" class="view">
|
||||
<h1>🎉 Passkey Added Successfully!</h1>
|
||||
<p>Your new passkey has been added to your account. You can now use it to log in.</p>
|
||||
<button onclick="window.location.href='/'" class="btn-primary">Go to Login</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const { startRegistration } = SimpleWebAuthnBrowser;
|
||||
|
||||
// Global state
|
||||
let currentToken = null;
|
||||
let currentUser = null;
|
||||
|
||||
// View management
|
||||
function showView(viewId) {
|
||||
document.querySelectorAll('.view').forEach(view => {
|
||||
view.classList.remove('active');
|
||||
});
|
||||
document.getElementById(viewId).classList.add('active');
|
||||
}
|
||||
|
||||
function showAddPasskeyView() {
|
||||
showView('addPasskeyView');
|
||||
clearStatus('addPasskeyStatus');
|
||||
}
|
||||
|
||||
function showCompleteView() {
|
||||
showView('completeView');
|
||||
}
|
||||
|
||||
// Status management
|
||||
function showStatus(elementId, message, type = 'info') {
|
||||
const statusEl = document.getElementById(elementId);
|
||||
statusEl.textContent = message;
|
||||
statusEl.className = `status ${type}`;
|
||||
statusEl.style.display = 'block';
|
||||
}
|
||||
|
||||
function clearStatus(elementId) {
|
||||
const statusEl = document.getElementById(elementId);
|
||||
statusEl.style.display = 'none';
|
||||
}
|
||||
|
||||
// Validate reset token and show add passkey view
|
||||
async function validateTokenAndShowAddView(token) {
|
||||
try {
|
||||
const response = await fetch('/api/validate-device-token', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ token })
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.error) {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
|
||||
currentToken = token;
|
||||
currentUser = result;
|
||||
document.getElementById('userName').textContent = result.user_name;
|
||||
showAddPasskeyView();
|
||||
|
||||
} catch (error) {
|
||||
showStatus('addPasskeyStatus', `Error: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Add new passkey via reset token
|
||||
async function addPasskeyWithToken(token) {
|
||||
try {
|
||||
const ws = await aWebSocket('/ws/add_device_credential');
|
||||
|
||||
// Send token to server
|
||||
ws.send(JSON.stringify({ token }));
|
||||
|
||||
// Get registration options
|
||||
const optionsJSON = JSON.parse(await ws.recv());
|
||||
if (optionsJSON.error) throw new Error(optionsJSON.error);
|
||||
|
||||
showStatus('addPasskeyStatus', 'Save new passkey to your authenticator...', 'info');
|
||||
|
||||
const registrationResponse = await startRegistration({ optionsJSON });
|
||||
ws.send(JSON.stringify(registrationResponse));
|
||||
|
||||
const result = JSON.parse(await ws.recv());
|
||||
if (result.error) throw new Error(`Server: ${result.error}`);
|
||||
|
||||
ws.close();
|
||||
|
||||
showCompleteView();
|
||||
|
||||
} catch (error) {
|
||||
showStatus('addPasskeyStatus', `Failed to add passkey: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Check URL path for token on page load
|
||||
function checkUrlParams() {
|
||||
const path = window.location.pathname;
|
||||
const pathParts = path.split('/');
|
||||
|
||||
// Check if URL is in format /reset/token
|
||||
if (pathParts.length >= 3 && pathParts[1] === 'reset') {
|
||||
const token = pathParts[2];
|
||||
if (token) {
|
||||
validateTokenAndShowAddView(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Form event handlers
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Check for token in URL
|
||||
checkUrlParams();
|
||||
|
||||
// Add passkey button
|
||||
const addPasskeyBtn = document.getElementById('addPasskeyBtn');
|
||||
|
||||
addPasskeyBtn.addEventListener('click', async () => {
|
||||
if (!currentToken) {
|
||||
showStatus('addPasskeyStatus', 'No valid device addition token found', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
addPasskeyBtn.disabled = true;
|
||||
clearStatus('addPasskeyStatus');
|
||||
|
||||
try {
|
||||
showStatus('addPasskeyStatus', 'Starting passkey registration...', 'info');
|
||||
await addPasskeyWithToken(currentToken);
|
||||
} catch (err) {
|
||||
showStatus('addPasskeyStatus', `Registration failed: ${err.message}`, 'error');
|
||||
} finally {
|
||||
addPasskeyBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user