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