Files
Oxicloud/static/js/views/device-verify/device-verify.js
T

137 lines
4.8 KiB
JavaScript
Raw Normal View History

2026-04-07 22:48:59 +02:00
// device-verify.js — Extracted from inline <script> in device-verify.html
import { getCsrfHeaders } from '../../core/csrf.js';
import { oxiIconsInit } from '../../core/icons.js';
2026-04-07 22:50:42 +02:00
(() => {
2026-04-07 22:48:59 +02:00
var API_BASE = window.location.origin;
2026-04-30 02:02:27 +02:00
var codeInput = /** @type {HTMLInputElement} */ (document.getElementById('user-code'));
2026-04-07 22:48:59 +02:00
var deviceInfo = document.getElementById('device-info');
var actionButtons = document.getElementById('action-buttons');
var errorText = document.getElementById('error-text');
2026-05-07 23:40:02 +02:00
var btnApprove = /** @type {HTMLButtonElement} */ (document.getElementById('btn-approve'));
var btnDeny = /** @type {HTMLButtonElement} */ (document.getElementById('btn-deny'));
/** @type {ReturnType<typeof setTimeout>} */
2026-04-07 22:48:59 +02:00
var debounceTimer = null;
var currentCode = '';
oxiIconsInit();
2026-04-07 22:48:59 +02:00
// Pre-fill from URL query param (?code=ABCD-1234)
var params = new URLSearchParams(window.location.search);
if (params.get('code')) {
codeInput.value = params.get('code');
lookupCode(params.get('code'));
}
// Auto-insert hyphen and lookup on input
2026-04-07 22:50:42 +02:00
codeInput.addEventListener('input', (e) => {
2026-05-07 23:40:02 +02:00
const target = /** @type {HTMLInputElement} */ (e.target);
var val = target.value.toUpperCase().replace(/[^A-Z0-9-]/g, '');
2026-04-07 22:48:59 +02:00
// Auto-insert hyphen after 4 chars
if (val.length === 4 && val.indexOf('-') === -1) {
2026-04-07 22:50:42 +02:00
val = `${val}-`;
2026-04-07 22:48:59 +02:00
}
2026-05-07 23:40:02 +02:00
target.value = val;
2026-04-07 22:48:59 +02:00
errorText.classList.add('hidden');
// Debounce lookup
clearTimeout(debounceTimer);
if (val.length >= 9) {
2026-04-07 22:50:42 +02:00
debounceTimer = setTimeout(() => {
2026-04-07 22:48:59 +02:00
lookupCode(val);
}, 300);
} else {
deviceInfo.classList.add('hidden');
actionButtons.classList.add('hidden');
}
});
// Wire up approve / deny buttons (replaces inline onclick)
2026-04-07 22:50:42 +02:00
btnApprove.addEventListener('click', () => {
2026-04-07 22:48:59 +02:00
handleAction('approve');
});
2026-04-07 22:50:42 +02:00
btnDeny.addEventListener('click', () => {
2026-04-07 22:48:59 +02:00
handleAction('deny');
});
2026-05-07 23:40:02 +02:00
/**
*
* @param {string} code
* @returns
*/
2026-04-07 22:48:59 +02:00
async function lookupCode(code) {
try {
2026-04-07 22:50:42 +02:00
const resp = await fetch(`${API_BASE}/api/auth/device/verify?code=${encodeURIComponent(code)}`, {
2026-04-07 22:48:59 +02:00
credentials: 'same-origin'
});
if (resp.status === 401) {
showError('You must be logged in to authorize a device. Please log in first.');
return;
}
if (!resp.ok) throw new Error('Lookup failed');
2026-04-07 22:50:42 +02:00
const data = await resp.json();
2026-04-07 22:48:59 +02:00
if (data.valid) {
currentCode = code;
document.getElementById('info-client').textContent = data.client_name || 'Unknown';
document.getElementById('info-scopes').textContent = data.scopes || 'all';
deviceInfo.classList.remove('hidden');
actionButtons.classList.remove('hidden');
errorText.classList.add('hidden');
} else {
deviceInfo.classList.add('hidden');
actionButtons.classList.add('hidden');
showError('Code not found or expired. Please check and try again.');
}
} catch (_err) {
showError('Failed to verify code. Please try again.');
}
}
2026-05-07 23:40:02 +02:00
/**
*
* @param {'approve' | 'deny'} action
*/
2026-04-07 22:48:59 +02:00
async function handleAction(action) {
btnApprove.disabled = true;
btnDeny.disabled = true;
try {
2026-04-07 22:50:42 +02:00
const resp = await fetch(`${API_BASE}/api/auth/device/verify`, {
2026-04-07 22:48:59 +02:00
method: 'POST',
credentials: 'same-origin',
headers: Object.assign({ 'Content-Type': 'application/json' }, getCsrfHeaders()),
body: JSON.stringify({ user_code: currentCode, action: action })
});
if (!resp.ok) {
2026-04-07 22:50:42 +02:00
const err = await resp.json().catch(() => {
2026-04-07 22:48:59 +02:00
return {};
});
throw new Error(err.message || 'Action failed');
}
document.getElementById('step-code').classList.add('hidden');
if (action === 'approve') {
document.getElementById('status-success').classList.remove('hidden');
} else {
document.getElementById('status-denied').classList.remove('hidden');
}
} catch (err) {
btnApprove.disabled = false;
btnDeny.disabled = false;
2026-05-07 23:40:02 +02:00
showError(/** @type {Error} */ (err).message || 'Failed to process action.');
2026-04-07 22:48:59 +02:00
}
}
2026-05-07 23:40:02 +02:00
/**
*
* @param {string} msg
*/
2026-04-07 22:48:59 +02:00
function showError(msg) {
errorText.textContent = msg;
errorText.classList.remove('hidden');
}
})();