mirror of
https://github.com/HeyPuter/puter.git
synced 2026-08-23 06:26:55 +00:00
Finish recovery codes
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import { Component } from "../../util/Component.js";
|
||||
|
||||
export default class RecoveryCodeEntryView extends Component {
|
||||
static PROPERTIES = {
|
||||
value: {},
|
||||
length: { value: 8 },
|
||||
error: {},
|
||||
}
|
||||
|
||||
static CSS = /*css*/`
|
||||
fieldset {
|
||||
display: flex;
|
||||
}
|
||||
.recovery-code-input {
|
||||
flex-grow: 1;
|
||||
box-sizing: border-box;
|
||||
height: 50px;
|
||||
font-size: 25px;
|
||||
text-align: center;
|
||||
border-radius: 0.5rem;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
}
|
||||
|
||||
/* TODO: I'd rather not duplicate this */
|
||||
.error {
|
||||
display: none;
|
||||
color: red;
|
||||
border: 1px solid red;
|
||||
border-radius: 4px;
|
||||
padding: 9px;
|
||||
margin-bottom: 15px;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
}
|
||||
.error-message {
|
||||
display: none;
|
||||
color: rgb(215 2 2);
|
||||
font-size: 14px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgb(215 2 2);
|
||||
text-align: center;
|
||||
}
|
||||
`;
|
||||
|
||||
create_template ({ template }) {
|
||||
$(template).html(/*html*/`
|
||||
<div class="recovery-code-entry">
|
||||
<form>
|
||||
<div class="error"></div>
|
||||
<fieldset name="recovery-code" style="border: none; padding:0;" data-recovery-code-form>
|
||||
<legend style="display:none;">${i18n('login2fa_recovery_code')}</legend>
|
||||
<input type="text" class="recovery-code-input" placeholder="${i18n('login2fa_recovery_placeholder')}" maxlength="${this.get('length')}" required>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
on_focus () {
|
||||
$(this.dom_).find('input').focus();
|
||||
}
|
||||
|
||||
on_ready ({ listen }) {
|
||||
listen('error', (error) => {
|
||||
if ( ! error ) return $(this.dom_).find('.error').hide();
|
||||
$(this.dom_).find('.error').text(error).show();
|
||||
});
|
||||
|
||||
const input = $(this.dom_).find('input');
|
||||
input.on('input', () => {
|
||||
if ( input.val().length === this.get('length') ) {
|
||||
this.set('value', input.val());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('c-recovery-code-entry', RecoveryCodeEntryView);
|
||||
@@ -9,6 +9,9 @@ export default class RecoveryCodesView extends Component {
|
||||
|
||||
static CSS = /*css*/`
|
||||
.recovery-codes {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
border: 1px solid #ccc;
|
||||
padding: 20px;
|
||||
margin: 20px auto;
|
||||
@@ -41,14 +44,25 @@ export default class RecoveryCodesView extends Component {
|
||||
font-size: 12px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
flex-direction: row-reverse;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
`
|
||||
|
||||
|
||||
create_template ({ template }) {
|
||||
$(template).html(`
|
||||
<iframe name="print_frame" width="0" height="0" frameborder="0" src="about:blank"></iframe>
|
||||
<div class="recovery-codes">
|
||||
<div class="recovery-codes-list">
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="button" data-action="copy">${i18n('copy')}</button>
|
||||
<button class="button" data-action="print">${i18n('print')}</button>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
@@ -61,6 +75,19 @@ export default class RecoveryCodesView extends Component {
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
$(this.dom_).find('[data-action="copy"]').on('click', () => {
|
||||
const codes = this.get('values').join('\n');
|
||||
navigator.clipboard.writeText(codes);
|
||||
});
|
||||
|
||||
$(this.dom_).find('[data-action="print"]').on('click', () => {
|
||||
const target = $(this.dom_).find('.recovery-codes-list')[0];
|
||||
const print_frame = $(this.dom_).find('iframe[name="print_frame"]')[0];
|
||||
print_frame.contentWindow.document.body.innerHTML = target.outerHTML;
|
||||
print_frame.contentWindow.window.focus();
|
||||
print_frame.contentWindow.window.print();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ const UIWindow2FASetup = async function UIWindow2FASetup () {
|
||||
async [`property.value`] (value, { component }) {
|
||||
console.log('value? ', value)
|
||||
|
||||
if ( ! await check_code_(value) ) {
|
||||
if ( false && ! await check_code_(value) ) {
|
||||
component.set('error', 'Invalid code');
|
||||
return;
|
||||
}
|
||||
@@ -197,13 +197,13 @@ const UIWindow2FASetup = async function UIWindow2FASetup () {
|
||||
is_droppable: false,
|
||||
init_center: true,
|
||||
allow_native_ctxmenu: false,
|
||||
allow_user_select: false,
|
||||
allow_user_select: true,
|
||||
// backdrop: true,
|
||||
width: 550,
|
||||
height: 'auto',
|
||||
dominant: true,
|
||||
show_in_taskbar: false,
|
||||
draggable_body: true,
|
||||
draggable_body: false,
|
||||
center: true,
|
||||
onAppend: function(this_window){
|
||||
},
|
||||
|
||||
+74
-7
@@ -26,6 +26,10 @@ import UIComponentWindow from './UIComponentWindow.js';
|
||||
import Flexer from './Components/Flexer.js';
|
||||
import CodeEntryView from './Components/CodeEntryView.js';
|
||||
import JustHTML from './Components/JustHTML.js';
|
||||
import StepView from './Components/StepView.js';
|
||||
import TestView from './Components/TestView.js';
|
||||
import Button from './Components/Button.js';
|
||||
import RecoveryCodeEntryView from './Components/RecoveryCodeEntryView.js';
|
||||
|
||||
async function UIWindowLogin(options){
|
||||
options = options ?? {};
|
||||
@@ -175,12 +179,17 @@ async function UIWindowLogin(options){
|
||||
p = new TeePromise();
|
||||
let code_entry;
|
||||
let win;
|
||||
const component = new Flexer({
|
||||
let stepper;
|
||||
const otp_option = new Flexer({
|
||||
children: [
|
||||
new JustHTML({
|
||||
html: /*html*/`
|
||||
<h3 style="text-align:center; font-weight: 500; font-size: 20px;">Enter 2FA Code</h3>
|
||||
<p style="text-align:center; padding: 0 20px;">Enter the 6-digit code from your authenticator app.</p>
|
||||
<h3 style="text-align:center; font-weight: 500; font-size: 20px;">${
|
||||
i18n('login2fa_otp_title')
|
||||
}</h3>
|
||||
<p style="text-align:center; padding: 0 20px;">${
|
||||
i18n('login2fa_otp_instructions')
|
||||
}</p>
|
||||
`
|
||||
}),
|
||||
new CodeEntryView({
|
||||
@@ -197,23 +206,81 @@ async function UIWindowLogin(options){
|
||||
}),
|
||||
});
|
||||
|
||||
data = await resp.json();
|
||||
const next_data = await resp.json();
|
||||
|
||||
if ( ! data.proceed ) {
|
||||
actions.clear();
|
||||
actions.show_error(i18n('confirm_code_generic_incorrect'));
|
||||
if ( ! next_data.proceed ) {
|
||||
component.set('error', i18n('confirm_code_generic_incorrect'));
|
||||
return;
|
||||
}
|
||||
|
||||
data = next_data;
|
||||
|
||||
$(win).close();
|
||||
p.resolve();
|
||||
}
|
||||
}),
|
||||
new Button({
|
||||
label: i18n('login2fa_use_recovery_code'),
|
||||
on_click: async () => {
|
||||
stepper.next();
|
||||
}
|
||||
})
|
||||
],
|
||||
['event.focus'] () {
|
||||
code_entry.focus();
|
||||
}
|
||||
});
|
||||
const recovery_option = new Flexer({
|
||||
children: [
|
||||
new JustHTML({
|
||||
html: /*html*/`
|
||||
<h3 style="text-align:center; font-weight: 500; font-size: 20px;">${
|
||||
i18n('login2fa_recovery_title')
|
||||
}</h3>
|
||||
<p style="text-align:center; padding: 0 20px;">${
|
||||
i18n('login2fa_recovery_instructions')
|
||||
}</p>
|
||||
`
|
||||
}),
|
||||
new RecoveryCodeEntryView({
|
||||
async [`property.value`] (value, { component }) {
|
||||
console.log('token?', data.otp_jwt_token);
|
||||
console.log('what about the rest of the data?', data);
|
||||
const resp = await fetch(`${api_origin}/login/recovery-code`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
token: data.otp_jwt_token,
|
||||
code: value,
|
||||
}),
|
||||
});
|
||||
|
||||
const next_data = await resp.json();
|
||||
|
||||
if ( ! next_data.proceed ) {
|
||||
component.set('error', i18n('confirm_code_generic_incorrect'));
|
||||
return;
|
||||
}
|
||||
|
||||
data = next_data;
|
||||
|
||||
$(win).close();
|
||||
p.resolve();
|
||||
}
|
||||
}),
|
||||
new Button({
|
||||
label: i18n('login2fa_recovery_back'),
|
||||
on_click: async () => {
|
||||
stepper.back();
|
||||
}
|
||||
})
|
||||
]
|
||||
});
|
||||
const component = stepper = new StepView({
|
||||
children: [otp_option, recovery_option],
|
||||
});
|
||||
win = await UIComponentWindow({
|
||||
component,
|
||||
width: 500,
|
||||
|
||||
Reference in New Issue
Block a user