9 changed files with 270 additions and 6 deletions
@ -0,0 +1,63 @@ |
|||
"use strict"; |
|||
/* eslint-env es2017, browser */ |
|||
/* global BASE_URL:readable, _post:readable */ |
|||
|
|||
function totpGenerate() { |
|||
fetch(`${BASE_URL}/totp/generate`, { |
|||
method: "POST", |
|||
mode: "same-origin", |
|||
credentials: "same-origin", |
|||
headers: { "Content-Type": "application/json" }, |
|||
}).then(resp => { |
|||
if (!resp.ok) { |
|||
return resp.text().then(body => Promise.reject(body)); |
|||
} |
|||
return resp.json(); |
|||
}).then(data => { |
|||
const enroll = document.getElementById("totp-enroll"); |
|||
enroll.dataset.secret = data.secret; |
|||
document.getElementById("totp-qr").innerHTML = data.qr_svg; |
|||
document.getElementById("totp-secret").textContent = data.secret; |
|||
enroll.classList.remove("d-none"); |
|||
document.getElementById("totp-generate").textContent = "Generate a new secret"; |
|||
document.getElementById("totp-enable-code").focus(); |
|||
}).catch(e => { |
|||
alert(`Failed to generate a TOTP secret\n${e}`); |
|||
}); |
|||
} |
|||
|
|||
function totpEnable() { |
|||
const secret = document.getElementById("totp-enroll").dataset.secret; |
|||
const code = document.getElementById("totp-enable-code").value.trim(); |
|||
if (!code) { |
|||
alert("Please enter the 6-digit code from your authenticator app"); |
|||
return; |
|||
} |
|||
_post(`${BASE_URL}/totp/enable`, |
|||
"2FA for the admin page is now enabled", |
|||
"Failed to enable 2FA", |
|||
JSON.stringify({ secret, code }) |
|||
); |
|||
} |
|||
|
|||
function totpDisable() { |
|||
const code = document.getElementById("totp-disable-code").value.trim(); |
|||
if (!code) { |
|||
alert("Please enter the current 6-digit code from your authenticator app"); |
|||
return; |
|||
} |
|||
if (!confirm("Really disable two-factor authentication for the admin page?")) { |
|||
return; |
|||
} |
|||
_post(`${BASE_URL}/totp/disable`, |
|||
"2FA for the admin page is now disabled", |
|||
"Failed to disable 2FA", |
|||
JSON.stringify({ code }) |
|||
); |
|||
} |
|||
|
|||
document.addEventListener("DOMContentLoaded", (/*event*/) => { |
|||
document.getElementById("totp-generate")?.addEventListener("click", totpGenerate); |
|||
document.getElementById("totp-enable")?.addEventListener("click", totpEnable); |
|||
document.getElementById("totp-disable")?.addEventListener("click", totpDisable); |
|||
}); |
|||
@ -0,0 +1,59 @@ |
|||
<main class="container-xxl"> |
|||
<div id="admin-totp" class="content col-lg-8"> |
|||
<div class="card shadow mb-4"> |
|||
<div class="card-header">Admin page two-factor authentication (TOTP)</div> |
|||
<div class="card-body"> |
|||
{{#if page_data.enabled}} |
|||
{{#if page_data.via_env}} |
|||
<p> |
|||
<span class="badge bg-success">Enabled</span> |
|||
via the <code>ADMIN_TOTP_SECRET</code> environment variable. |
|||
</p> |
|||
<p class="mb-0">To disable or change it, update the environment variable and restart Vaultwarden.</p> |
|||
{{else}} |
|||
<p><span class="badge bg-success">Enabled</span></p> |
|||
<p>Logging in to this admin page requires a time-based one-time code from your authenticator app.</p> |
|||
<div class="row g-2"> |
|||
<div class="col-auto"> |
|||
<input type="text" id="totp-disable-code" class="form-control" inputmode="numeric" |
|||
autocomplete="one-time-code" maxlength="6" placeholder="Current 2FA code"> |
|||
</div> |
|||
<div class="col-auto"> |
|||
<button type="button" id="totp-disable" class="btn btn-danger">Disable 2FA</button> |
|||
</div> |
|||
</div> |
|||
{{/if}} |
|||
{{else}} |
|||
<p><span class="badge bg-danger">Disabled</span></p> |
|||
<p> |
|||
Protect this admin page with a second factor: generate a secret, scan the QR code with your |
|||
authenticator app and confirm with a code. It takes effect immediately, no restart needed. |
|||
</p> |
|||
<button type="button" id="totp-generate" class="btn btn-primary">Enable 2FA</button> |
|||
<div id="totp-enroll" class="d-none mt-4"> |
|||
<div class="row"> |
|||
<div class="col-auto mb-3"> |
|||
<div id="totp-qr" class="bg-white p-2 rounded border d-inline-block"></div> |
|||
</div> |
|||
<div class="col"> |
|||
<p>Scan the QR code with your authenticator app, or enter the secret manually:</p> |
|||
<p><code id="totp-secret"></code></p> |
|||
<p>Then enter the current code from the app to verify and activate:</p> |
|||
<div class="row g-2"> |
|||
<div class="col-auto"> |
|||
<input type="text" id="totp-enable-code" class="form-control" inputmode="numeric" |
|||
autocomplete="one-time-code" maxlength="6" placeholder="6-digit code"> |
|||
</div> |
|||
<div class="col-auto"> |
|||
<button type="button" id="totp-enable" class="btn btn-success">Verify & activate</button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
{{/if}} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</main> |
|||
<script src="{{urlpath}}/vw_static/admin_totp.js"></script> |
|||
Loading…
Reference in new issue