mirror of
https://github.com/HeyPuter/puter
synced 2024-11-14 22:06:00 +00:00
Add recovery codes
This commit is contained in:
parent
455d3946d6
commit
3bf7737790
@ -26,13 +26,24 @@ module.exports = eggspress('/auth/configure-2fa/:action', {
|
||||
|
||||
actions.setup = async () => {
|
||||
const svc_otp = x.get('services').get('otp');
|
||||
|
||||
// generate secret
|
||||
const result = svc_otp.create_secret();
|
||||
|
||||
// generate recovery codes
|
||||
result.codes = [];
|
||||
for ( let i = 0; i < 10; i++ ) {
|
||||
result.codes.push(svc_otp.create_recovery_code());
|
||||
}
|
||||
|
||||
// update user
|
||||
await db.write(
|
||||
`UPDATE user SET otp_secret = ? WHERE uuid = ?`,
|
||||
[result.secret, user.uuid]
|
||||
`UPDATE user SET otp_secret = ?, otp_recovery_codes = ? WHERE uuid = ?`,
|
||||
[result.secret, result.codes.join(','), user.uuid]
|
||||
);
|
||||
// update cached user
|
||||
req.user.otp_secret = result.secret;
|
||||
req.user.otp_recovery_codes = result.codes.join(',');
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
@ -48,7 +59,7 @@ module.exports = eggspress('/auth/configure-2fa/:action', {
|
||||
|
||||
actions.disable = async () => {
|
||||
await db.write(
|
||||
`UPDATE user SET otp_enabled = 0 WHERE uuid = ?`,
|
||||
`UPDATE user SET otp_enabled = 0, otp_recovery_codes = '' WHERE uuid = ?`,
|
||||
[user.uuid]
|
||||
);
|
||||
return { success: true };
|
||||
|
@ -26,6 +26,16 @@ class OTPService extends BaseService {
|
||||
};
|
||||
}
|
||||
|
||||
create_recovery_code () {
|
||||
const require = this.require;
|
||||
const crypto = require('crypto');
|
||||
const { encode } = require('hi-base32');
|
||||
|
||||
const buffer = crypto.randomBytes(6);
|
||||
const code = encode(buffer).replace(/=/g, "").substring(0, 8);
|
||||
return code;
|
||||
}
|
||||
|
||||
verify (secret, code) {
|
||||
const require = this.require;
|
||||
const otpauth = require('otpauth');
|
||||
|
@ -64,10 +64,10 @@ export default {
|
||||
i18n('confirm_2fa_setup'),
|
||||
i18n('confirm_2fa_recovery'),
|
||||
],
|
||||
recovery_codes: data.codes,
|
||||
has_confirm_and_cancel: true,
|
||||
});
|
||||
|
||||
console.log('confirmation?', confirmation);
|
||||
|
||||
if ( ! confirmation ) return;
|
||||
|
||||
await fetch(`${api_origin}/auth/configure-2fa/enable`, {
|
||||
|
@ -20,6 +20,8 @@
|
||||
import TeePromise from '../util/TeePromise.js';
|
||||
import UIWindow from './UIWindow.js'
|
||||
|
||||
let checkbox_id_ = 0;
|
||||
|
||||
async function UIWindowQR(options){
|
||||
const confirmations = options.confirmations || [];
|
||||
|
||||
@ -36,24 +38,45 @@ async function UIWindowQR(options){
|
||||
}</h1>`;
|
||||
h += `</div>`;
|
||||
|
||||
if ( options.recovery_codes ) {
|
||||
h += `<div class="recovery-codes">`;
|
||||
h += `<h2 style="text-align: center; font-size: 16px; padding: 10px; font-weight: 400; margin: -10px 10px 20px 10px; -webkit-font-smoothing: antialiased; color: #5f626d;">${
|
||||
i18n('recovery_codes')
|
||||
}</h2>`;
|
||||
h += `<div class="recovery-codes-list">`;
|
||||
for ( let i=0 ; i < options.recovery_codes.length ; i++ ) {
|
||||
h += `<div class="recovery-code">${
|
||||
html_encode(options.recovery_codes[i])
|
||||
}</div>`;
|
||||
}
|
||||
h += `</div>`;
|
||||
h += `</div>`;
|
||||
}
|
||||
|
||||
for ( let i=0 ; i < confirmations.length ; i++ ) {
|
||||
const confirmation = confirmations[i];
|
||||
// checkbox
|
||||
h += `<div class="qr-code-checkbox">`;
|
||||
h += `<input type="checkbox" name="confirmation_${i}">`;
|
||||
h += `<label for="confirmation_${i}">${confirmation}</label>`;
|
||||
h += `<input type="checkbox" id="checkbox_${++checkbox_id_}" name="confirmation_${i}">`;
|
||||
h += `<label for="checkbox_${checkbox_id_}">${confirmation}</label>`;
|
||||
h += `</div>`;
|
||||
}
|
||||
|
||||
// h += `<button class="code-confirm-btn" style="margin: 20px auto; display: block; width: 100%; padding: 10px; font-size: 16px; font-weight: 400; background-color: #007bff; color: #fff; border: none; border-radius: 5px; cursor: pointer;">${
|
||||
// i18n('confirm')
|
||||
// }</button>`;
|
||||
h += `<button type="submit" class="button button-block button-primary code-confirm-btn" style="margin-top:10px;" disabled>${
|
||||
i18n('confirm')
|
||||
}</button>`;
|
||||
h += `<button type="submit" class="button button-block button-secondary code-cancel-btn" style="margin-top:10px;">${
|
||||
i18n('cancel')
|
||||
}</button>`;
|
||||
if ( options.has_confirm_and_cancel ) {
|
||||
h += `<button type="submit" class="button button-block button-primary code-confirm-btn" style="margin-top:10px;" disabled>${
|
||||
i18n('confirm')
|
||||
}</button>`;
|
||||
h += `<button type="submit" class="button button-block button-secondary code-cancel-btn" style="margin-top:10px;">${
|
||||
i18n('cancel')
|
||||
}</button>`;
|
||||
} else {
|
||||
h += `<button type="submit" class="button button-block button-primary code-confirm-btn" style="margin-top:10px;">${
|
||||
i18n('done')
|
||||
}</button>`;
|
||||
}
|
||||
|
||||
const el_window = await UIWindow({
|
||||
title: 'Instant Login!',
|
||||
|
@ -2606,11 +2606,55 @@ label {
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
height: 520px;
|
||||
}
|
||||
|
||||
.otp-qr-code img {
|
||||
width: 355px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.recovery-codes {
|
||||
border: 1px solid #ccc;
|
||||
padding: 20px;
|
||||
margin: 20px auto;
|
||||
width: 90%;
|
||||
max-width: 600px;
|
||||
background-color: #f9f9f9;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.recovery-codes h2 {
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
color: #333;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.recovery-codes-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||||
gap: 10px; /* Adds space between grid items */
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.recovery-code {
|
||||
background-color: #fff;
|
||||
border: 1px solid #ddd;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 12px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.qr-code-checkbox {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.qr-code-checkbox input[type=checkbox] {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.perm-title {
|
||||
|
@ -47,7 +47,7 @@ const en = {
|
||||
color: 'Color',
|
||||
hue: 'Hue',
|
||||
confirm_2fa_setup: 'I have added the code to my authenticator app',
|
||||
confirm_2fa_recovery: 'I have saved my recovery codes',
|
||||
confirm_2fa_recovery: 'I have saved my recovery codes in a secure location',
|
||||
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",
|
||||
@ -212,6 +212,7 @@ const en = {
|
||||
save_session: 'Save session',
|
||||
save_session_c2a: 'Create an account to save your current session and avoid losing your work.',
|
||||
scan_qr_c2a: 'Scan the code below to log into this session from other devices',
|
||||
scan_qr_2fa: 'Scan the QR code with your authenticator app',
|
||||
scan_qr_generic: 'Scan this QR code using your phone or another device',
|
||||
seconds: 'seconds',
|
||||
security: "Security",
|
||||
|
Loading…
Reference in New Issue
Block a user