mirror of
https://github.com/pi-hole/web.git
synced 2025-12-20 02:38:28 +00:00
Add app password support (#2793)
This commit is contained in:
@@ -40,6 +40,7 @@ $(function () {
|
|||||||
{ data: "id" },
|
{ data: "id" },
|
||||||
{ data: "valid", render: renderBool },
|
{ data: "valid", render: renderBool },
|
||||||
{ data: null },
|
{ data: null },
|
||||||
|
{ data: "app", render: renderBool },
|
||||||
{ data: "login_at", render: utils.renderTimestamp },
|
{ data: "login_at", render: utils.renderTimestamp },
|
||||||
{ data: "valid_until", render: utils.renderTimestamp },
|
{ data: "valid_until", render: utils.renderTimestamp },
|
||||||
{ data: "remote_addr", type: "ip-address" },
|
{ data: "remote_addr", type: "ip-address" },
|
||||||
@@ -84,10 +85,10 @@ $(function () {
|
|||||||
'">' +
|
'">' +
|
||||||
'<span class="far fa-trash-alt"></span>' +
|
'<span class="far fa-trash-alt"></span>' +
|
||||||
"</button>";
|
"</button>";
|
||||||
$("td:eq(8)", row).html(button);
|
$("td:eq(9)", row).html(button);
|
||||||
if (data.current_session) {
|
if (data.current_session) {
|
||||||
ownSessionID = data.id;
|
ownSessionID = data.id;
|
||||||
$("td:eq(6)", row).html(
|
$("td:eq(7)", row).html(
|
||||||
'<strong title="This is the session you are currently using for the web interface">' +
|
'<strong title="This is the session you are currently using for the web interface">' +
|
||||||
data.remote_addr +
|
data.remote_addr +
|
||||||
"</strong>"
|
"</strong>"
|
||||||
@@ -311,6 +312,69 @@ $("#modal-totp").on("shown.bs.modal", function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var apppwhash = null;
|
||||||
|
$("#modal-apppw").on("shown.bs.modal", function () {
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/auth/app",
|
||||||
|
})
|
||||||
|
.done(function (data) {
|
||||||
|
apppwhash = data.app.hash;
|
||||||
|
$("#password_code").text(data.app.password);
|
||||||
|
$("#password_display").removeClass("hidden");
|
||||||
|
$("#password-spinner").hide();
|
||||||
|
})
|
||||||
|
.fail(function (data) {
|
||||||
|
apiFailure(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#apppw_submit").on("click", function () {
|
||||||
|
// Enable app password
|
||||||
|
setAppPassword();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#apppw_clear").on("click", function () {
|
||||||
|
// Disable app password
|
||||||
|
apppwhash = "";
|
||||||
|
setAppPassword();
|
||||||
|
});
|
||||||
|
|
||||||
|
function setAppPassword() {
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/config",
|
||||||
|
type: "PATCH",
|
||||||
|
dataType: "json",
|
||||||
|
processData: false,
|
||||||
|
data: JSON.stringify({ config: { webserver: { api: { app_pwhash: apppwhash } } } }),
|
||||||
|
contentType: "application/json",
|
||||||
|
})
|
||||||
|
.done(function () {
|
||||||
|
$("#modal-apppw").modal("hide");
|
||||||
|
const verb = apppwhash.length > 0 ? "enabled" : "disabled";
|
||||||
|
const verb2 = apppwhash.length > 0 ? "will" : "may";
|
||||||
|
alert(
|
||||||
|
"App password has been " +
|
||||||
|
verb +
|
||||||
|
", you " +
|
||||||
|
verb2 +
|
||||||
|
" need to re-login to continue using the web interface."
|
||||||
|
);
|
||||||
|
location.reload();
|
||||||
|
})
|
||||||
|
.fail(function (data) {
|
||||||
|
apiFailure(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove class "password_background" from the password input field with ID
|
||||||
|
// password_code when the user hovers over it or if it is focused
|
||||||
|
$("#password_code").on("mouseover focus", function () {
|
||||||
|
$(this).removeClass("password_background");
|
||||||
|
});
|
||||||
|
$("#password_code").on("mouseout blur", function () {
|
||||||
|
$(this).addClass("password_background");
|
||||||
|
});
|
||||||
|
|
||||||
// Trigger keyup event when pasting into the TOTP code input field
|
// Trigger keyup event when pasting into the TOTP code input field
|
||||||
$("#totp_code").on("paste", function (e) {
|
$("#totp_code").on("paste", function (e) {
|
||||||
$(e.target).keyup();
|
$(e.target).keyup();
|
||||||
|
|||||||
@@ -136,8 +136,11 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-12 pt-3">
|
||||||
<button type="button" id="button-enable-totp" class="btn btn-success hidden" data-toggle="modal" data-target="#modal-totp">Enable 2FA</button>
|
<button type="button" id="button-enable-totp" class="btn btn-success hidden" data-toggle="modal" data-target="#modal-totp">Enable 2FA</button>
|
||||||
<button type="button" id="button-disable-totp" class="btn btn-danger hidden" data-toggle="modal" data-target="#modal-totp-disable">Disable 2FA</button>
|
<button type="button" id="button-disable-totp" class="btn btn-danger hidden" data-toggle="modal" data-target="#modal-totp-disable">Disable 2FA</button>
|
||||||
|
<button type="button" id="button-apppw" class="btn btn-default pull-right" data-toggle="modal" data-target="#modal-apppw">Configure app password</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -158,6 +161,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
|
|||||||
<th title="Session ID used internally by our Pi-hole">ID</th>
|
<th title="Session ID used internally by our Pi-hole">ID</th>
|
||||||
<th title="Session is still valid (neither expired nor closed)">Valid</th>
|
<th title="Session is still valid (neither expired nor closed)">Valid</th>
|
||||||
<th title="Connection between client and Pi-hole is end-to-end encrypted">TLS</th>
|
<th title="Connection between client and Pi-hole is end-to-end encrypted">TLS</th>
|
||||||
|
<th title="Session used application password during authentication"><i class="fas fa-robot"></i></th>
|
||||||
<th title="Time at which the client created the session">Login at</th>
|
<th title="Time at which the client created the session">Login at</th>
|
||||||
<th title="Time at which the session expires (if not prolonged)">Valid until</th>
|
<th title="Time at which the session expires (if not prolonged)">Valid until</th>
|
||||||
<th title="The client that created this session (the current connection is highlighted in bold-face)">Client IP</th>
|
<th title="The client that created this session (the current connection is highlighted in bold-face)">Client IP</th>
|
||||||
@@ -217,6 +221,39 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="modal fade" id="modal-apppw" style="display: none;">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span></button>
|
||||||
|
<h4 class="modal-title">Configure application password</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>After you turn on two-factor (2FA) verification and set up an
|
||||||
|
Authenticator app, you may run into issues if you use apps or
|
||||||
|
other services that don't support two-step verification. In this
|
||||||
|
case, you can create and use an app password to sign in.</p>
|
||||||
|
<p>An app password is a long, randomly generated password that
|
||||||
|
can be used instead of your regular password + 2FA token when
|
||||||
|
signing in to the API. You can revoke the app password at any
|
||||||
|
time. The app password can also be used when 2FA is not enabled.</p>
|
||||||
|
<div class="text-center">
|
||||||
|
<i id="password-spinner" class="fas fa-spinner fa-pulse fa-5x"></i>
|
||||||
|
</div>
|
||||||
|
<p id="password_display" class="text-center hidden">🔐<br><strong>Your new app password is:</strong><br><code class="password_background m-5" id="password_code"></code></p>
|
||||||
|
<p>IMPORTANT: The app password generated here will only be shown
|
||||||
|
once and cannot be recovered. Make sure to store it in a safe
|
||||||
|
place!</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" id="apppw_clear" class="btn btn-default btn-danger pull-left">Remove currently set app password</button>
|
||||||
|
<button type="button" id="apppw_submit" class="btn btn-default btn-success">Enable new app password</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="<?=pihole.fileversion('scripts/vendor/jquery.confirm.min.js')?>"></script>
|
<script src="<?=pihole.fileversion('scripts/vendor/jquery.confirm.min.js')?>"></script>
|
||||||
<script src="<?=pihole.fileversion('scripts/vendor/qrious.min.js')?>"></script>
|
<script src="<?=pihole.fileversion('scripts/vendor/qrious.min.js')?>"></script>
|
||||||
<script src="<?=pihole.fileversion('scripts/pi-hole/js/settings-api.js')?>"></script>
|
<script src="<?=pihole.fileversion('scripts/pi-hole/js/settings-api.js')?>"></script>
|
||||||
|
|||||||
@@ -1173,3 +1173,220 @@ table.dataTable tbody > tr > .selected {
|
|||||||
.button-pad {
|
.button-pad {
|
||||||
margin: 2px;
|
margin: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.m-0 {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
.m-1 {
|
||||||
|
margin: 0.25rem !important;
|
||||||
|
}
|
||||||
|
.m-2 {
|
||||||
|
margin: 0.5rem !important;
|
||||||
|
}
|
||||||
|
.m-3 {
|
||||||
|
margin: 1rem !important;
|
||||||
|
}
|
||||||
|
.m-4 {
|
||||||
|
margin: 1.5rem !important;
|
||||||
|
}
|
||||||
|
.m-5 {
|
||||||
|
margin: 3rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-0 {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
}
|
||||||
|
.mr-0 {
|
||||||
|
margin-right: 0 !important;
|
||||||
|
}
|
||||||
|
.mb-0 {
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
.ml-0 {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
}
|
||||||
|
.mx-0 {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
margin-right: 0 !important;
|
||||||
|
}
|
||||||
|
.my-0 {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-1 {
|
||||||
|
margin-top: 0.25rem !important;
|
||||||
|
}
|
||||||
|
.mr-1 {
|
||||||
|
margin-right: 0.25rem !important;
|
||||||
|
}
|
||||||
|
.mb-1 {
|
||||||
|
margin-bottom: 0.25rem !important;
|
||||||
|
}
|
||||||
|
.ml-1 {
|
||||||
|
margin-left: 0.25rem !important;
|
||||||
|
}
|
||||||
|
.mx-1 {
|
||||||
|
margin-left: 0.25rem !important;
|
||||||
|
margin-right: 0.25rem !important;
|
||||||
|
}
|
||||||
|
.my-1 {
|
||||||
|
margin-top: 0.25rem !important;
|
||||||
|
margin-bottom: 0.25rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-2 {
|
||||||
|
margin-top: 0.5rem !important;
|
||||||
|
}
|
||||||
|
.mr-2 {
|
||||||
|
margin-right: 0.5rem !important;
|
||||||
|
}
|
||||||
|
.mb-2 {
|
||||||
|
margin-bottom: 0.5rem !important;
|
||||||
|
}
|
||||||
|
.ml-2 {
|
||||||
|
margin-left: 0.5rem !important;
|
||||||
|
}
|
||||||
|
.mx-2 {
|
||||||
|
margin-right: 0.5rem !important;
|
||||||
|
margin-left: 0.5rem !important;
|
||||||
|
}
|
||||||
|
.my-2 {
|
||||||
|
margin-top: 0.5rem !important;
|
||||||
|
margin-bottom: 0.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-3 {
|
||||||
|
margin-top: 1rem !important;
|
||||||
|
}
|
||||||
|
.mr-3 {
|
||||||
|
margin-right: 1rem !important;
|
||||||
|
}
|
||||||
|
.mb-3 {
|
||||||
|
margin-bottom: 1rem !important;
|
||||||
|
}
|
||||||
|
.ml-3 {
|
||||||
|
margin-left: 1rem !important;
|
||||||
|
}
|
||||||
|
.mx-3 {
|
||||||
|
margin-right: 1rem !important;
|
||||||
|
margin-left: 1rem !important;
|
||||||
|
}
|
||||||
|
.my-3 {
|
||||||
|
margin-bottom: 1rem !important;
|
||||||
|
margin-top: 1rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-0 {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
.p-1 {
|
||||||
|
padding: 0.25rem !important;
|
||||||
|
}
|
||||||
|
.p-2 {
|
||||||
|
padding: 0.5rem !important;
|
||||||
|
}
|
||||||
|
.p-3 {
|
||||||
|
padding: 1rem !important;
|
||||||
|
}
|
||||||
|
.p-4 {
|
||||||
|
padding: 1.5rem !important;
|
||||||
|
}
|
||||||
|
.p-5 {
|
||||||
|
padding: 3rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pt-0 {
|
||||||
|
padding-top: 0 !important;
|
||||||
|
}
|
||||||
|
.pr-0 {
|
||||||
|
padding-right: 0 !important;
|
||||||
|
}
|
||||||
|
.pb-0 {
|
||||||
|
padding-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
.pl-0 {
|
||||||
|
padding-left: 0 !important;
|
||||||
|
}
|
||||||
|
.px-0 {
|
||||||
|
padding-left: 0 !important;
|
||||||
|
padding-right: 0 !important;
|
||||||
|
}
|
||||||
|
.py-0 {
|
||||||
|
padding-top: 0 !important;
|
||||||
|
padding-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pt-1 {
|
||||||
|
padding-top: 0.25rem !important;
|
||||||
|
}
|
||||||
|
.pr-1 {
|
||||||
|
padding-right: 0.25rem !important;
|
||||||
|
}
|
||||||
|
.pb-1 {
|
||||||
|
padding-bottom: 0.25rem !important;
|
||||||
|
}
|
||||||
|
.pl-1 {
|
||||||
|
padding-left: 0.25rem !important;
|
||||||
|
}
|
||||||
|
.px-1 {
|
||||||
|
padding-left: 0.25rem !important;
|
||||||
|
padding-right: 0.25rem !important;
|
||||||
|
}
|
||||||
|
.py-1 {
|
||||||
|
padding-top: 0.25rem !important;
|
||||||
|
padding-bottom: 0.25rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pt-2 {
|
||||||
|
padding-top: 0.5rem !important;
|
||||||
|
}
|
||||||
|
.pr-2 {
|
||||||
|
padding-right: 0.5rem !important;
|
||||||
|
}
|
||||||
|
.pb-2 {
|
||||||
|
padding-bottom: 0.5rem !important;
|
||||||
|
}
|
||||||
|
.pl-2 {
|
||||||
|
padding-left: 0.5rem !important;
|
||||||
|
}
|
||||||
|
.px-2 {
|
||||||
|
padding-right: 0.5rem !important;
|
||||||
|
padding-left: 0.5rem !important;
|
||||||
|
}
|
||||||
|
.py-2 {
|
||||||
|
padding-top: 0.5rem !important;
|
||||||
|
padding-bottom: 0.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pt-3 {
|
||||||
|
padding-top: 1rem !important;
|
||||||
|
}
|
||||||
|
.pr-3 {
|
||||||
|
padding-right: 1rem !important;
|
||||||
|
}
|
||||||
|
.pb-3 {
|
||||||
|
padding-bottom: 1rem !important;
|
||||||
|
}
|
||||||
|
.pl-3 {
|
||||||
|
padding-left: 1rem !important;
|
||||||
|
}
|
||||||
|
.px-3 {
|
||||||
|
padding-right: 1rem !important;
|
||||||
|
padding-left: 1rem !important;
|
||||||
|
}
|
||||||
|
.py-3 {
|
||||||
|
padding-bottom: 1rem !important;
|
||||||
|
padding-top: 1rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.password_background {
|
||||||
|
background-image: repeating-linear-gradient(
|
||||||
|
45deg,
|
||||||
|
white 0%,
|
||||||
|
white 2%,
|
||||||
|
rgb(0, 0, 0) 2%,
|
||||||
|
rgb(0, 0, 0) 4%,
|
||||||
|
white 4%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user