Add 2fa setting and complete login flow

This commit is contained in:
KernelDeimos
2024-05-01 17:54:23 -04:00
parent abc12bd407
commit 2dfecb5287
8 changed files with 160 additions and 18 deletions

View File

@@ -31,6 +31,8 @@ module.exports = eggspress('/auth/configure-2fa/:action', {
`UPDATE user SET otp_secret = ? WHERE uuid = ?`,
[result.secret, user.uuid]
);
// update cached user
req.user.otp_secret = result.secret;
return result;
};

View File

@@ -41,6 +41,8 @@ const WHOAMI_GET = eggspress('/whoami', {
const is_user = actor.type instanceof UserActorType;
console.log('user?', req.user);
// send user object
const details = {
username: req.user.username,
@@ -54,6 +56,7 @@ const WHOAMI_GET = eggspress('/whoami', {
is_temp: (req.user.password === null && req.user.email === null),
taskbar_items: await get_taskbar_items(req.user),
referral_code: req.user.referral_code,
otp: !! req.user.otp_secret,
...(req.new_token ? { token: req.token } : {})
};

View File

@@ -64,14 +64,6 @@ export default {
h += `</div>`;
}
// session manager
h += `<div class="settings-card">`;
h += `<strong>${i18n('sessions')}</strong>`;
h += `<div style="flex-grow:1;">`;
h += `<button class="button manage-sessions" style="float:right;">${i18n('manage_sessions')}</button>`;
h += `</div>`;
h += `</div>`;
// 'Delete Account' button
h += `<div class="settings-card settings-card-danger">`;
h += `<strong style="display: inline-block;">${i18n("delete_account")}</strong>`;

View File

@@ -0,0 +1,85 @@
import UIWindowQR from "../UIWindowQR.js";
export default {
id: 'security',
title_i18n_key: 'security',
icon: 'shield.svg',
html: () => {
let h = `<h1>${i18n('security')}</h1>`;
// change password button
if(!user.is_temp){
h += `<div class="settings-card">`;
h += `<strong>${i18n('password')}</strong>`;
h += `<div style="flex-grow:1;">`;
h += `<button class="button change-password" style="float:right;">${i18n('change_password')}</button>`;
h += `</div>`;
h += `</div>`;
}
// session manager
h += `<div class="settings-card">`;
h += `<strong>${i18n('sessions')}</strong>`;
h += `<div style="flex-grow:1;">`;
h += `<button class="button manage-sessions" style="float:right;">${i18n('manage_sessions')}</button>`;
h += `</div>`;
h += `</div>`;
// configure 2FA
if(!user.is_temp){
h += `<div class="settings-card">`;
h += `<div>`;
h += `<strong style="display:block;">${i18n('two_factor')}</strong>`;
h += `<span class="user-otp-state" style="display:block; margin-top:5px;">${
i18n(user.otp ? 'two_factor_enabled' : 'two_factor_disabled')
}</span>`;
h += `</div>`;
h += `<div style="flex-grow:1;">`;
h += `<button class="button enable-2fa" style="float:right;${user.otp ? 'display:none;' : ''}">${i18n('enable_2fa')}</button>`;
h += `<button class="button disable-2fa" style="float:right;${user.otp ? '' : 'display:none;'}">${i18n('disable_2fa')}</button>`;
h += `</div>`;
h += `</div>`;
}
return h;
},
init: ($el_window) => {
$el_window.find('.enable-2fa').on('click', async function (e) {
const resp = await fetch(`${api_origin}/auth/configure-2fa/enable`, {
method: 'POST',
headers: {
Authorization: `Bearer ${puter.authToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({}),
});
const data = await resp.json();
UIWindowQR({
message_i18n_key: 'scan_qr_2fa',
text: data.url,
text_below: data.secret,
});
$el_window.find('.enable-2fa').hide();
$el_window.find('.disable-2fa').show();
$el_window.find('.user-otp-state').text(i18n('two_factor_enabled'));
});
$el_window.find('.disable-2fa').on('click', async function (e) {
const resp = await fetch(`${api_origin}/auth/configure-2fa/disable`, {
method: 'POST',
headers: {
Authorization: `Bearer ${puter.authToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({}),
});
$el_window.find('.enable-2fa').show();
$el_window.find('.disable-2fa').hide();
$el_window.find('.user-otp-state').text(i18n('two_factor_disabled'));
});
}
}

View File

@@ -21,6 +21,7 @@ import UIWindow from '../UIWindow.js'
import AboutTab from './UITabAbout.js';
import UsageTab from './UITabUsage.js';
import AccountTab from './UITabAccount.js';
import SecurityTab from './UITabSecurity.js';
import PersonalizationTab from './UITabPersonalization.js';
import LanguageTab from './UITabLanguage.js';
import ClockTab from './UITabClock.js';
@@ -33,6 +34,7 @@ async function UIWindowSettings(options){
AboutTab,
UsageTab,
AccountTab,
SecurityTab,
PersonalizationTab,
LanguageTab,
ClockTab,

View File

@@ -21,6 +21,8 @@ import UIWindow from './UIWindow.js'
import UIWindowSignup from './UIWindowSignup.js'
import UIWindowRecoverPassword from './UIWindowRecoverPassword.js'
import UIWindowVerificationCode from './UIWindowVerificationCode.js';
import TeePromise from '../util/TeePromise.js';
import UIAlert from './UIAlert.js';
async function UIWindowLogin(options){
options = options ?? {};
@@ -165,11 +167,45 @@ async function UIWindowLogin(options){
contentType: "application/json",
data: data,
success: async function (data){
let p = Promise.resolve();
if ( data.next_step === 'otp' ) {
const value = await UIWindowVerificationCode();
console.log('got value', value);
p = new TeePromise();
UIWindowVerificationCode({
title_key: 'confirm_code_2fa_title',
instruction_key: 'confirm_code_2fa_instruction',
submit_btn_key: 'confirm_code_2fa_submit_btn',
on_value: async ({ actions, win, value }) => {
try {
const resp = await fetch(`${api_origin}/login/otp`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
token: data.otp_jwt_token,
code: value,
}),
});
data = await resp.json();
if ( ! data.proceed ) {
actions.clear();
actions.show_error(i18n('confirm_code_generic_incorrect'));
return;
}
$(win).close();
p.resolve();
} catch (e) {
actions.show_error(e.message ?? i18n('error_unknown_cause'));
}
},
});
}
await p;
window.update_auth_data(data.token, data.user);
if(options.reload_on_success){

View File

@@ -7,7 +7,6 @@ const UIWindowVerificationCode = async function UIWindowVerificationCode ( optio
let is_checking_code = false;
const html_title = i18n(options.title_key || 'confirm_code_generic_title');
const html_confirm = i18n(options.confirm_key || 'confirm_code_generic_confirm');
const html_instruction = i18n(options.instruction_key || 'confirm_code_generic_instruction');
const submit_btn_txt = i18n(options.submit_btn_key || 'confirm_code_generic_submit');
@@ -29,9 +28,6 @@ const UIWindowVerificationCode = async function UIWindowVerificationCode ( optio
</fieldset>`;
h += `<button type="submit" class="button button-block button-primary code-confirm-btn" style="margin-top:10px;" disabled>${submit_btn_txt}</button>`;
h += `</form>`;
h += `<div style="text-align:center; padding:10px; font-size:14px; margin-top:10px;">`;
h += `<span class="send-conf-code">what is this text</span>`;
h += `</div>`;
h += `</div>`;
const el_window = await UIWindow({
@@ -69,11 +65,23 @@ const UIWindowVerificationCode = async function UIWindowVerificationCode ( optio
}
});
const p = new TeePromise();
$(el_window).find('.digit-input').first().focus();
const actions = {
clear: () => {
final_code = '';
$(el_window).find('.code-confirm-btn').prop('disabled', false);
$(el_window).find('.code-confirm-btn').html(submit_btn_txt);
$(el_window).find('.digit-input').val('');
$(el_window).find('.digit-input').first().focus();
},
show_error: (msg) => {
$(el_window).find('.error').html(html_encode(msg));
$(el_window).find('.error').fadeIn();
}
};
$(el_window).find('.code-confirm-btn').on('click submit', function(e){
e.preventDefault();
e.stopPropagation();
@@ -92,7 +100,11 @@ const UIWindowVerificationCode = async function UIWindowVerificationCode ( optio
setTimeout(() => {
console.log('final code', final_code);
p.resolve(final_code);
options.on_value({
actions,
value: final_code,
win: el_window
});
}, 1000);
})

View File

@@ -47,7 +47,10 @@ const en = {
color: 'Color',
hue: 'Hue',
confirm_account_for_free_referral_storage_c2a: 'Create an account and confirm your email address to receive 1 GB of free storage. Your friend will get 1 GB of free storage too.',
confirm_code_generic_incorrect: "Incorrect Code.",
confirm_code_generic_title: "Enter Confirmation Code",
confirm_code_2fa_instruction: "Enter the 6-digit code from your authenticator app.",
confirm_code_2fa_submit_btn: "Submit",
confirm_code_2fa_title: "Enter 2FA Code",
confirm_delete_multiple_items: 'Are you sure you want to permanently delete these items?',
confirm_delete_single_item: 'Do you want to permanently delete this item?',
@@ -83,6 +86,7 @@ const en = {
desktop_background_fit: "Fit",
developers: "Developers",
dir_published_as_website: `%strong% has been published to:`,
disable_2fa: 'Disable 2FA',
disassociate_dir: "Disassociate Directory",
download: 'Download',
download_file: 'Download File',
@@ -95,10 +99,12 @@ const en = {
empty_trash: 'Empty Trash',
empty_trash_confirmation: `Are you sure you want to permanently delete the items in Trash?`,
emptying_trash: 'Emptying Trash…',
enable_2fa: 'Enable 2FA',
end_hard: "End Hard",
end_process_force_confirm: "Are you sure you want to force-quit this process?",
end_soft: "End Soft",
enter_password_to_confirm_delete_user: "Enter your password to confirm account deletion",
error_unknown_cause: "An unknown error occurred.",
feedback: "Feedback",
feedback_c2a: "Please use the form below to send us your feedback, comments, and bug reports.",
feedback_sent_confirmation: "Thank you for contacting us. If you have an email associated with your account, you will hear back from us as soon as possible.",
@@ -206,6 +212,7 @@ const en = {
scan_qr_c2a: 'Scan the code below to log into this session from other devices',
scan_qr_generic: 'Scan this QR code using your phone or another device',
seconds: 'seconds',
security: "Security",
select: "Select",
selected: 'selected',
select_color: 'Select color…',
@@ -238,6 +245,9 @@ const en = {
tos_fineprint: `By clicking 'Create Free Account' you agree to Puter's {{link=terms}}Terms of Service{{/link}} and {{link=privacy}}Privacy Policy{{/link}}.`,
transparency: "Transparency",
trash: 'Trash',
two_factor: 'Two Factor Authentication',
two_factor_disabled: '2FA Disabled',
two_factor_enabled: '2FA Enabled',
type: 'Type',
type_confirm_to_delete_account: "Type 'confirm' to delete your account.",
ui_colors: "UI Colors",