mirror of
https://github.com/HeyPuter/puter.git
synced 2026-03-20 19:52:23 -05:00
Add 2fa setting and complete login flow
This commit is contained in:
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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 } : {})
|
||||
};
|
||||
|
||||
|
||||
@@ -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>`;
|
||||
|
||||
85
src/UI/Settings/UITabSecurity.js
Normal file
85
src/UI/Settings/UITabSecurity.js
Normal 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'));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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);
|
||||
})
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user