configure_2fa.mako
153 lines
| 5.8 KiB
| application/x-mako
|
MakoHtmlLexer
r5360 | <%inherit file="base/root.mako"/> | |||
<%def name="title()"> | ||||
${_('Setup authenticator app')} | ||||
%if c.rhodecode_name: | ||||
· ${h.branding(c.rhodecode_name)} | ||||
%endif | ||||
</%def> | ||||
<style>body{background-color:#eeeeee;}</style> | ||||
<div class="loginbox"> | ||||
<div class="header-account"> | ||||
<div id="header-inner" class="title"> | ||||
<div id="logo"> | ||||
% if c.rhodecode_name: | ||||
<div class="branding"> | ||||
<a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a> | ||||
</div> | ||||
% endif | ||||
</div> | ||||
</div> | ||||
</div> | ||||
<div class="loginwrapper"> | ||||
<h1>Setup the authenticator app</h1> | ||||
<p>Authenticator apps like <a href='https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2' target="_blank" rel="noopener noreferrer">Google Authenticator</a>, etc. generate one-time passwords that are used as a second factor to verify you identity.</p> | ||||
<rhodecode-toast id="notifications"></rhodecode-toast> | ||||
<div id="setup_2fa"> | ||||
<div class="sign-in-title"> | ||||
<h1>${_('Scan the QR code')}</h1> | ||||
</div> | ||||
<p>Use an authenticator app to scan.</p> | ||||
<img src="data:image/png;base64, ${qr}"/> | ||||
<p>${_('Unable to scan?')} <a id="toggleLink">${_('Click here')}</a></p> | ||||
<div id="secretDiv" class="hidden"> | ||||
<p>${_('Copy and use this code to manually setup an authenticator app')}</p> | ||||
<input type="text" id="secretField" value=${key}> | ||||
<i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="" title="${_('Copy the secret key')}"></i> | ||||
</div> | ||||
<div id="codesPopup" class="modal"> | ||||
<div class="modal-content"> | ||||
<ul id="recoveryCodesList"></ul> | ||||
<button id="copyAllBtn" class="btn btn-primary">Copy All</button> | ||||
</div> | ||||
</div> | ||||
<br><br> | ||||
<div id="verify_2fa"> | ||||
${h.secure_form(h.route_path('setup_2fa'), request=request, id='totp_form')} | ||||
<div class="form mt-4"> | ||||
<div class="field"> | ||||
<p> | ||||
<div class="label"> | ||||
<label for="totp" class="form-label text-dark font-weight-bold" style="text-align: left;">${_('Verify the code from the app')}:</label> | ||||
</div> | ||||
</p> | ||||
<p> | ||||
<div> | ||||
<div class="input-group"> | ||||
${h.text('totp', class_='form-control', style='width: 40%;')} | ||||
<div id="formErrors"> | ||||
%if 'totp' in errors: | ||||
<span class="error-message">${errors.get('totp')}</span> | ||||
<br /> | ||||
%endif | ||||
</div> | ||||
<div class="input-group-append"> | ||||
${h.submit('save',_('Verify'),class_="btn btn-primary", style='width: 40%;', disabled=not codes_viewed)} | ||||
</div> | ||||
</div> | ||||
</div> | ||||
</p> | ||||
</div> | ||||
</div> | ||||
</div> | ||||
</div> | ||||
</div> | ||||
</div> | ||||
<script> | ||||
document.addEventListener('DOMContentLoaded', function() { | ||||
let clipboardIcons = document.querySelectorAll('.clipboard-action'); | ||||
clipboardIcons.forEach(function(icon) { | ||||
icon.addEventListener('click', function() { | ||||
var inputField = document.getElementById('secretField'); | ||||
inputField.select(); | ||||
document.execCommand('copy'); | ||||
}); | ||||
}); | ||||
}); | ||||
</script> | ||||
<script> | ||||
document.getElementById('toggleLink').addEventListener('click', function() { | ||||
let hiddenField = document.getElementById('secretDiv'); | ||||
if (hiddenField.classList.contains('hidden')) { | ||||
hiddenField.classList.remove('hidden'); | ||||
} | ||||
}); | ||||
</script> | ||||
<script> | ||||
const recovery_codes_string = '${recovery_codes}'; | ||||
const cleaned_recovery_codes_string = recovery_codes_string | ||||
.replace(/"/g, '"') | ||||
.replace(/'/g, "'"); | ||||
const recovery_codes = JSON.parse(cleaned_recovery_codes_string); | ||||
const cleaned_recovery_codes = recovery_codes.map(code => code.replace(/['"]/g, '')); | ||||
function showRecoveryCodesPopup() { | ||||
const popup = document.getElementById("codesPopup"); | ||||
const codesList = document.getElementById("recoveryCodesList"); | ||||
const verify_btn = document.getElementById('save') | ||||
if (verify_btn.disabled) { | ||||
codesList.innerHTML = ""; | ||||
cleaned_recovery_codes.forEach(code => { | ||||
const listItem = document.createElement("li"); | ||||
listItem.textContent = code; | ||||
codesList.appendChild(listItem); | ||||
}); | ||||
popup.style.display = "block"; | ||||
verify_btn.disabled = false; | ||||
} | ||||
} | ||||
document.getElementById("save").addEventListener("mouseover", showRecoveryCodesPopup); | ||||
const popup = document.getElementById("codesPopup"); | ||||
const closeButton = document.querySelector(".close"); | ||||
window.onclick = function(event) { | ||||
if (event.target === popup || event.target === closeButton) { | ||||
popup.style.display = "none"; | ||||
} | ||||
} | ||||
document.getElementById("copyAllBtn").addEventListener("click", function() { | ||||
const codesListItems = document.querySelectorAll("#recoveryCodesList li"); | ||||
const allCodes = Array.from(codesListItems).map(item => item.textContent).join(", "); | ||||
const textarea = document.createElement('textarea'); | ||||
textarea.value = allCodes; | ||||
document.body.appendChild(textarea); | ||||
textarea.select(); | ||||
document.execCommand('copy'); | ||||
document.body.removeChild(textarea); | ||||
}); | ||||
</script> | ||||