// Approving a sign-in that a terminal started, at /device?code=XXXX-XXXX. // // The terminal (the TUI) shows a code and a link to this page. Whoever opens it // is already signed in — by the provider or by password, whichever the login // page offered — and is asked to approve. Approving hands that terminal a // session for *this* account, so the page names the account and the code, and // tells anybody who did not start this to refuse. import * as api from './api.js'; import { h, clear } from './ui.js'; import { state } from './state.js'; import { navigate } from './app.js'; const view = () => document.getElementById('view-device'); // What has been decided for the code on screen, so a re-render does not offer // to approve it a second time. let outcome = null; // { code, approved } export function show() { render(); } function render() { const code = new URLSearchParams(location.search).get('code') || ''; if (!code) return clear(view(), enterCode()); if (outcome && outcome.code === code) return clear(view(), decided(outcome.approved)); return clear(view(), confirmCard(code)); } // Reached without a code, for somebody who typed the address by hand. function enterCode() { const input = h('input', { name: 'code', autocomplete: 'off', autocapitalize: 'characters', spellcheck: 'false', placeholder: 'XXXX-XXXX', required: true, }); const form = h('form', { class: 'card device-card' }, h('h2', { text: 'Sign in a terminal' }), h('p', { class: 'muted', text: 'Enter the code the terminal is showing.' }), h('label', {}, h('span', { text: 'Code' }), input), h('button', { class: 'btn btn-primary', type: 'submit', text: 'Continue' })); form.addEventListener('submit', (e) => { e.preventDefault(); navigate(`/device?code=${encodeURIComponent(input.value.trim())}`); }); return form; } function confirmCard(code) { const err = h('p', { class: 'form-error', role: 'alert', hidden: true }); const approve = h('button', { class: 'btn btn-primary', type: 'button', text: 'Approve' }); const refuse = h('button', { class: 'btn', type: 'button', text: 'Refuse' }); const decide = (approved) => async () => { err.hidden = true; approve.disabled = refuse.disabled = true; try { await (approved ? api.approveDevice(code) : api.denyDevice(code)); outcome = { code, approved }; render(); } catch (ex) { err.textContent = ex.message; err.hidden = false; approve.disabled = refuse.disabled = false; } }; approve.addEventListener('click', decide(true)); refuse.addEventListener('click', decide(false)); return h('div', { class: 'card device-card' }, h('h2', { text: 'Sign in a terminal?' }), h('p', {}, 'A terminal is asking to sign in as ', h('strong', { text: state.me.user.username }), '. Check that this code matches the one it is showing:'), h('p', { class: 'device-code', text: code }), h('p', { class: 'muted small', text: 'Only approve a sign-in you started yourself. Whoever is approved here acts as you.' }), err, h('div', { class: 'row-actions' }, approve, refuse)); } function decided(approved) { return h('div', { class: 'card device-card' }, h('h2', { text: approved ? 'Approved' : 'Refused' }), h('p', { class: 'muted', text: approved ? 'You can go back to your terminal. It signs in within a few seconds.' : 'That terminal will not be signed in.' }), h('a', { class: 'btn', href: '/', text: 'Go to the queue' })); }