mirror of
https://github.com/HeyPuter/puter
synced 2024-11-15 06:15:47 +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 () => {
|
actions.setup = async () => {
|
||||||
const svc_otp = x.get('services').get('otp');
|
const svc_otp = x.get('services').get('otp');
|
||||||
|
|
||||||
|
// generate secret
|
||||||
const result = svc_otp.create_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(
|
await db.write(
|
||||||
`UPDATE user SET otp_secret = ? WHERE uuid = ?`,
|
`UPDATE user SET otp_secret = ?, otp_recovery_codes = ? WHERE uuid = ?`,
|
||||||
[result.secret, user.uuid]
|
[result.secret, result.codes.join(','), user.uuid]
|
||||||
);
|
);
|
||||||
// update cached user
|
|
||||||
req.user.otp_secret = result.secret;
|
req.user.otp_secret = result.secret;
|
||||||
|
req.user.otp_recovery_codes = result.codes.join(',');
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -48,7 +59,7 @@ module.exports = eggspress('/auth/configure-2fa/:action', {
|
|||||||
|
|
||||||
actions.disable = async () => {
|
actions.disable = async () => {
|
||||||
await db.write(
|
await db.write(
|
||||||
`UPDATE user SET otp_enabled = 0 WHERE uuid = ?`,
|
`UPDATE user SET otp_enabled = 0, otp_recovery_codes = '' WHERE uuid = ?`,
|
||||||
[user.uuid]
|
[user.uuid]
|
||||||
);
|
);
|
||||||
return { success: true };
|
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) {
|
verify (secret, code) {
|
||||||
const require = this.require;
|
const require = this.require;
|
||||||
const otpauth = require('otpauth');
|
const otpauth = require('otpauth');
|
||||||
|
@ -64,10 +64,10 @@ export default {
|
|||||||
i18n('confirm_2fa_setup'),
|
i18n('confirm_2fa_setup'),
|
||||||
i18n('confirm_2fa_recovery'),
|
i18n('confirm_2fa_recovery'),
|
||||||
],
|
],
|
||||||
|
recovery_codes: data.codes,
|
||||||
|
has_confirm_and_cancel: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('confirmation?', confirmation);
|
|
||||||
|
|
||||||
if ( ! confirmation ) return;
|
if ( ! confirmation ) return;
|
||||||
|
|
||||||
await fetch(`${api_origin}/auth/configure-2fa/enable`, {
|
await fetch(`${api_origin}/auth/configure-2fa/enable`, {
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
import TeePromise from '../util/TeePromise.js';
|
import TeePromise from '../util/TeePromise.js';
|
||||||
import UIWindow from './UIWindow.js'
|
import UIWindow from './UIWindow.js'
|
||||||
|
|
||||||
|
let checkbox_id_ = 0;
|
||||||
|
|
||||||
async function UIWindowQR(options){
|
async function UIWindowQR(options){
|
||||||
const confirmations = options.confirmations || [];
|
const confirmations = options.confirmations || [];
|
||||||
|
|
||||||
@ -36,24 +38,45 @@ async function UIWindowQR(options){
|
|||||||
}</h1>`;
|
}</h1>`;
|
||||||
h += `</div>`;
|
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++ ) {
|
for ( let i=0 ; i < confirmations.length ; i++ ) {
|
||||||
const confirmation = confirmations[i];
|
const confirmation = confirmations[i];
|
||||||
// checkbox
|
// checkbox
|
||||||
h += `<div class="qr-code-checkbox">`;
|
h += `<div class="qr-code-checkbox">`;
|
||||||
h += `<input type="checkbox" name="confirmation_${i}">`;
|
h += `<input type="checkbox" id="checkbox_${++checkbox_id_}" name="confirmation_${i}">`;
|
||||||
h += `<label for="confirmation_${i}">${confirmation}</label>`;
|
h += `<label for="checkbox_${checkbox_id_}">${confirmation}</label>`;
|
||||||
h += `</div>`;
|
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;">${
|
// 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')
|
// i18n('confirm')
|
||||||
// }</button>`;
|
// }</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>${
|
h += `<button type="submit" class="button button-block button-primary code-confirm-btn" style="margin-top:10px;" disabled>${
|
||||||
i18n('confirm')
|
i18n('confirm')
|
||||||
}</button>`;
|
}</button>`;
|
||||||
h += `<button type="submit" class="button button-block button-secondary code-cancel-btn" style="margin-top:10px;">${
|
h += `<button type="submit" class="button button-block button-secondary code-cancel-btn" style="margin-top:10px;">${
|
||||||
i18n('cancel')
|
i18n('cancel')
|
||||||
}</button>`;
|
}</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({
|
const el_window = await UIWindow({
|
||||||
title: 'Instant Login!',
|
title: 'Instant Login!',
|
||||||
|
@ -2606,11 +2606,55 @@ label {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 520px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.otp-qr-code img {
|
.otp-qr-code img {
|
||||||
width: 355px;
|
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 {
|
.perm-title {
|
||||||
|
@ -47,7 +47,7 @@ const en = {
|
|||||||
color: 'Color',
|
color: 'Color',
|
||||||
hue: 'Hue',
|
hue: 'Hue',
|
||||||
confirm_2fa_setup: 'I have added the code to my authenticator app',
|
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_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_incorrect: "Incorrect Code.",
|
||||||
confirm_code_generic_title: "Enter Confirmation Code",
|
confirm_code_generic_title: "Enter Confirmation Code",
|
||||||
@ -212,6 +212,7 @@ const en = {
|
|||||||
save_session: 'Save session',
|
save_session: 'Save session',
|
||||||
save_session_c2a: 'Create an account to save your current session and avoid losing your work.',
|
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_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',
|
scan_qr_generic: 'Scan this QR code using your phone or another device',
|
||||||
seconds: 'seconds',
|
seconds: 'seconds',
|
||||||
security: "Security",
|
security: "Security",
|
||||||
|
Loading…
Reference in New Issue
Block a user