Merge branch 'nightly' into feature/bootstrap5
This commit is contained in:
@@ -208,9 +208,69 @@ function recursiveBase64StrToArrayBuffer(obj) {
|
||||
keyboard: false
|
||||
}).show();
|
||||
|
||||
|
||||
// validate Time based OTP tfa
|
||||
$("#pending_tfa_tab_totp").click(function(){
|
||||
$(".webauthn-authenticator-selection").removeClass("active");
|
||||
$("#collapseWebAuthnTFA").collapse('hide');
|
||||
|
||||
// select default if only one authenticator exists
|
||||
if ($('.totp-authenticator-selection').length == 1){
|
||||
$('.totp-authenticator-selection').addClass("active");
|
||||
var id = $('.totp-authenticator-selection').children('input').first().val();
|
||||
$("#totp_selected_id").val(id);
|
||||
$("#collapseTotpTFA").collapse('show');
|
||||
}
|
||||
});
|
||||
$(".totp-authenticator-selection").click(function(){
|
||||
$(".totp-authenticator-selection").removeClass("active");
|
||||
$(this).addClass("active");
|
||||
|
||||
var id = $(this).children('input').first().val();
|
||||
$("#totp_selected_id").val(id);
|
||||
|
||||
$("#collapseTotpTFA").collapse('show');
|
||||
});
|
||||
if ($('.totp-authenticator-selection').length == 1 &&
|
||||
$('#pending_tfa_tab_yubi_otp').length == 0 &&
|
||||
$('.webauthn-authenticator-selection').length == 0){
|
||||
|
||||
// select default if only one authenticator exists
|
||||
$('.totp-authenticator-selection').addClass("active");
|
||||
|
||||
var id = $('.totp-authenticator-selection').children('input').first().val();
|
||||
$("#totp_selected_id").val(id);
|
||||
|
||||
$("#collapseTotpTFA").collapse('show');
|
||||
setTimeout(function() { $("#collapseTotpTFA").find('input[name="token"]').focus(); }, 1000);
|
||||
}
|
||||
$('#pending_tfa_tab_totp').on('shown.bs.tab', function() {
|
||||
// autofocus
|
||||
setTimeout(function() { $("#collapseTotpTFA").find('input[name="token"]').focus(); }, 200);
|
||||
});
|
||||
// validate Yubi OTP tfa
|
||||
if ($('.webauthn-authenticator-selection').length == 0){
|
||||
// autofocus
|
||||
setTimeout(function() { $("#collapseYubiTFA").find('input[name="token"]').focus(); }, 1000);
|
||||
}
|
||||
$('#pending_tfa_tab_yubi_otp').on('shown.bs.tab', function() {
|
||||
// autofocus
|
||||
$("#collapseYubiTFA").find('input[name="token"]').focus();
|
||||
});
|
||||
// validate WebAuthn tfa
|
||||
$('#start_webauthn_confirmation').click(function(){
|
||||
$('#webauthn_status_auth').html('<p><i class="bi bi-arrow-repeat icon-spin"></i> ' + lang_tfa.init_webauthn + '</p>');
|
||||
$("#pending_tfa_tab_webauthn").click(function(){
|
||||
$(".totp-authenticator-selection").removeClass("active");
|
||||
|
||||
$("#collapseTotpTFA").collapse('hide');
|
||||
});
|
||||
$(".webauthn-authenticator-selection").click(function(){
|
||||
$(".webauthn-authenticator-selection").removeClass("active");
|
||||
$(this).addClass("active");
|
||||
|
||||
var id = $(this).children('input').first().val();
|
||||
$("#webauthn_selected_id").val(id);
|
||||
|
||||
$("#collapseWebAuthnTFA").collapse('show');
|
||||
|
||||
$(this).find('input[name=token]').focus();
|
||||
if(document.getElementById("webauthn_auth_data") !== null) {
|
||||
@@ -224,30 +284,32 @@ function recursiveBase64StrToArrayBuffer(obj) {
|
||||
window.fetch("/api/v1/get/webauthn-tfa-get-args", {method:'GET',cache:'no-cache'}).then(response => {
|
||||
return response.json();
|
||||
}).then(json => {
|
||||
if (json.success === false) throw new Error();
|
||||
console.log(json);
|
||||
if (json.success === false) throw new Error();
|
||||
if (json.type === "error") throw new Error(json.msg);
|
||||
|
||||
recursiveBase64StrToArrayBuffer(json);
|
||||
return json;
|
||||
recursiveBase64StrToArrayBuffer(json);
|
||||
return json;
|
||||
}).then(getCredentialArgs => {
|
||||
// get credentials
|
||||
return navigator.credentials.get(getCredentialArgs);
|
||||
// get credentials
|
||||
return navigator.credentials.get(getCredentialArgs);
|
||||
}).then(cred => {
|
||||
return {
|
||||
id: cred.rawId ? arrayBufferToBase64(cred.rawId) : null,
|
||||
clientDataJSON: cred.response.clientDataJSON ? arrayBufferToBase64(cred.response.clientDataJSON) : null,
|
||||
authenticatorData: cred.response.authenticatorData ? arrayBufferToBase64(cred.response.authenticatorData) : null,
|
||||
signature : cred.response.signature ? arrayBufferToBase64(cred.response.signature) : null
|
||||
};
|
||||
return {
|
||||
id: cred.rawId ? arrayBufferToBase64(cred.rawId) : null,
|
||||
clientDataJSON: cred.response.clientDataJSON ? arrayBufferToBase64(cred.response.clientDataJSON) : null,
|
||||
authenticatorData: cred.response.authenticatorData ? arrayBufferToBase64(cred.response.authenticatorData) : null,
|
||||
signature : cred.response.signature ? arrayBufferToBase64(cred.response.signature) : null
|
||||
};
|
||||
}).then(JSON.stringify).then(function(AuthenticatorAttestationResponse) {
|
||||
// send request by submit
|
||||
var form = document.getElementById('webauthn_auth_form');
|
||||
var auth = document.getElementById('webauthn_auth_data');
|
||||
auth.value = AuthenticatorAttestationResponse;
|
||||
form.submit();
|
||||
// send request by submit
|
||||
var form = document.getElementById('webauthn_auth_form');
|
||||
var auth = document.getElementById('webauthn_auth_data');
|
||||
auth.value = AuthenticatorAttestationResponse;
|
||||
form.submit();
|
||||
}).catch(function(err) {
|
||||
var webauthn_return_code = document.getElementById('webauthn_return_code');
|
||||
webauthn_return_code.style.display = webauthn_return_code.style.display === 'none' ? '' : null;
|
||||
webauthn_return_code.innerHTML = lang_tfa.error_code + ': ' + err + ' ' + lang_tfa.reload_retry;
|
||||
var webauthn_return_code = document.getElementById('webauthn_return_code');
|
||||
webauthn_return_code.style.display = webauthn_return_code.style.display === 'none' ? '' : null;
|
||||
webauthn_return_code.innerHTML = lang_tfa.error_code + ': ' + err + ' ' + lang_tfa.reload_retry;
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -263,7 +325,9 @@ function recursiveBase64StrToArrayBuffer(obj) {
|
||||
}
|
||||
});
|
||||
});
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
// Validate FIDO2
|
||||
$("#fido2-login").click(function(){
|
||||
$('#fido2-alerts').html();
|
||||
@@ -384,11 +448,13 @@ function recursiveBase64StrToArrayBuffer(obj) {
|
||||
|
||||
$("#start_webauthn_register").click(() => {
|
||||
var key_id = document.getElementsByName('key_id')[1].value;
|
||||
var confirm_password = document.getElementsByName('confirm_password')[1].value;
|
||||
|
||||
// fetch WebAuthn create args
|
||||
window.fetch("/api/v1/get/webauthn-tfa-registration/{{ mailcow_cc_username|url_encode(true)|default('null') }}", {method:'GET',cache:'no-cache'}).then(response => {
|
||||
return response.json();
|
||||
}).then(json => {
|
||||
console.log(json);
|
||||
if (json.success === false) throw new Error(json.msg);
|
||||
recursiveBase64StrToArrayBuffer(json);
|
||||
|
||||
@@ -401,7 +467,8 @@ function recursiveBase64StrToArrayBuffer(obj) {
|
||||
clientDataJSON: cred.response.clientDataJSON ? arrayBufferToBase64(cred.response.clientDataJSON) : null,
|
||||
attestationObject: cred.response.attestationObject ? arrayBufferToBase64(cred.response.attestationObject) : null,
|
||||
key_id: key_id,
|
||||
tfa_method: "webauthn"
|
||||
tfa_method: "webauthn",
|
||||
confirm_password: confirm_password
|
||||
};
|
||||
}).then(JSON.stringify).then(AuthenticatorAttestationResponse => {
|
||||
// send request
|
||||
@@ -449,13 +516,20 @@ function recursiveBase64StrToArrayBuffer(obj) {
|
||||
{% if ui_texts.ui_footer %}
|
||||
<hr><span class="rot-enc">{{ ui_texts.ui_footer|rot13|raw }}</span>
|
||||
{% endif %}
|
||||
{% if mailcow_cc_username and mailcow_info.version_tag|default %}
|
||||
{% if mailcow_cc_username and mailcow_info.mailcow_branch|lower == "master" and mailcow_info.version_tag|default %}
|
||||
<span class="version">
|
||||
🐮 + 🐋 = 💕
|
||||
<a href="{{ mailcow_info.project_url }}/releases/tag/{{ mailcow_info.version_tag }}" target="_blank">
|
||||
Version: {{ mailcow_info.version_tag }}
|
||||
Version: <a href="{{ mailcow_info.git_project_url }}/releases/tag/{{ mailcow_info.version_tag }}" target="_blank">{{ mailcow_info.version_tag }}
|
||||
</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if mailcow_cc_username and mailcow_info.mailcow_branch|lower == "nightly" and mailcow_info.version_tag|default %}
|
||||
<span class="version">
|
||||
🛠️🐮 + 🐋 = 💕
|
||||
Nightly: <a href="{{ mailcow_info.git_project_url }}/commit/{{ mailcow_info.git_commit }}" target="_blank">{{ mailcow_info.version_tag }}
|
||||
</a><br>
|
||||
<span style="text-align:right;display:block;">Build: {{ mailcow_info.git_commit_date }}</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</body>
|
||||
|
@@ -28,7 +28,7 @@
|
||||
<div class="col-sm-9 col-7">
|
||||
<select id="selectTFA" class="selectpicker" title="{{ lang.tfa.select }}">
|
||||
<option value="yubi_otp">{{ lang.tfa.yubi_otp }}</option>
|
||||
<option value="u2f">{{ lang.tfa.u2f }}</option>
|
||||
<option value="webauthn">{{ lang.tfa.webauthn }}</option>
|
||||
<option value="totp">{{ lang.tfa.totp }}</option>
|
||||
<option value="none">{{ lang.tfa.none }}</option>
|
||||
</select>
|
||||
|
@@ -131,37 +131,37 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if pending_tfa_method %}
|
||||
{% if pending_tfa_methods %}
|
||||
<div class="modal fade" id="ConfirmTFAModal" tabindex="-1" role="dialog" aria-labelledby="ConfirmTFAModalLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span></button>
|
||||
<h3 class="modal-title">{{ lang.tfa[pending_tfa_method] }}</h3>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{% if pending_tfa_method == 'yubi_otp' %}
|
||||
<form role="form" method="post">
|
||||
<div>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text" id="yubi-addon"><img alt="Yubicon Icon" src="/img/yubi.ico"></span>
|
||||
<span class="input-group-addon" id="yubi-addon"><img alt="Yubicon Icon" src="/img/yubi.ico"></span>
|
||||
<input type="text" name="token" class="form-control" autocomplete="off" placeholder="Touch Yubikey" aria-describedby="yubi-addon">
|
||||
<input type="hidden" name="tfa_method" value="yubi_otp">
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-sm d-block d-sm-inline btn-sm btn-secondary" type="submit" name="verify_tfa_login">{{ lang.login.login }}</button>
|
||||
<button class="btn btn-sm visible-xs-block visible-sm-inline visible-md-inline visible-lg-inline btn-sm btn-default" type="submit" name="verify_tfa_login">{{ lang.login.login }}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if pending_tfa_method == 'totp' %}
|
||||
<form role="form" method="post">
|
||||
<div>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text" id="tfa-addon"><i class="bi bi-shield-lock-fill"></i></span>
|
||||
<span class="input-group-addon" id="tfa-addon"><i class="bi bi-shield-lock-fill"></i></span>
|
||||
<input type="number" min="000000" max="999999" name="token" class="form-control" placeholder="123456" autocomplete="one-time-code" aria-describedby="tfa-addon">
|
||||
<input type="hidden" name="tfa_method" value="totp">
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-sm d-block d-sm-inline btn-secondary" type="submit" name="verify_tfa_login">{{ lang.login.login }}</button>
|
||||
<button class="btn btn-sm visible-xs-block visible-sm-inline visible-md-inline visible-lg-inline btn-default" type="submit" name="verify_tfa_login">{{ lang.login.login }}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if pending_tfa_method == 'hotp' %}
|
||||
|
@@ -435,11 +435,11 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="text-muted">{{ lang.add.syncjob_hint }}</p>
|
||||
<form class="form-horizontal" data-cached-form="true" role="form" data-id="add_syncjob">
|
||||
<form class="form-horizontal" data-cached-form="false" role="form" data-id="add_syncjob">
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2 text-sm-end" for="username">{{ lang.add.username }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select data-live-search="true" name="username" required>
|
||||
<select data-live-search="true" name="username" title="{{ lang.add.select }}" required>
|
||||
{% for mailbox in mailboxes %}
|
||||
<option>{{ mailbox }}</option>
|
||||
{% endfor %}
|
||||
|
@@ -53,6 +53,27 @@
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
{# TFA #}
|
||||
<div class="row">
|
||||
<div class="col-sm-3 col-xs-5 text-right">{{ lang.tfa.tfa }}:</div>
|
||||
<div class="col-sm-9 col-xs-7">
|
||||
<p id="tfa_pretty">{{ tfa_data.pretty }}</p>
|
||||
{% include 'tfa_keys.twig' %}
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 col-xs-5 text-right">{{ lang.tfa.set_tfa }}:</div>
|
||||
<div class="col-sm-9 col-xs-7">
|
||||
<select data-style="btn btn-sm dropdown-toggle bs-placeholder btn-default" data-width="fit" id="selectTFA" class="selectpicker" title="{{ lang.tfa.select }}">
|
||||
<option value="yubi_otp">{{ lang.tfa.yubi_otp }}</option>
|
||||
<option value="webauthn">{{ lang.tfa.webauthn }}</option>
|
||||
<option value="totp">{{ lang.tfa.totp }}</option>
|
||||
<option value="none">{{ lang.tfa.none }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
{# FIDO2 #}
|
||||
<div class="row">
|
||||
<div class="col-sm-3 col-12 text-sm-end text-start">
|
||||
|
Reference in New Issue
Block a user