v3.0.2 init commit

+ Fixed zeroSSL bug (said by @yeungalan ) #45
+ Fixed manual renew button bug
+ Seperated geodb module with access controller
+ Added per hosts access control (experimental) #69
+ Fixed basic auth not working on TLS bypass mode bug
+ Fixed empty domain crash bug #120
This commit is contained in:
Toby Chui
2024-04-14 19:37:01 +08:00
parent a000893dd1
commit 8e648a8e1f
34 changed files with 2689 additions and 1243 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -237,7 +237,7 @@
msgbox("Certificate installed successfully");
if (callback != undefined){
callback(false);
callback(true);
}
}
},

View File

@@ -8,7 +8,7 @@
background-color: #00ca52 !important;
}
</style>
<div style="width: 100%; overflow-x: auto; margin-bottom: 1em;">
<div style="width: 100%; overflow-x: auto; margin-bottom: 1em; min-height: 300px;">
<table class="ui celled sortable unstackable compact table">
<thead>
<tr>
@@ -16,7 +16,7 @@
<th>Destination</th>
<th>Virtual Directory</th>
<th>Basic Auth</th>
<th class="no-sort" style="min-width:100px;">Actions</th>
<th class="no-sort" style="min-width:150px;">Actions</th>
</tr>
</thead>
<tbody id="httpProxyList">
@@ -30,6 +30,8 @@
</div>
<script>
/* List all proxy endpoints */
function listProxyEndpoints(){
$.get("/api/proxy/list?type=host", function(data){
$("#httpProxyList").html(``);
@@ -79,10 +81,15 @@
}
$("#httpProxyList").append(`<tr eptuuid="${subd.RootOrMatchingDomain}" payload="${subdData}" class="subdEntry">
<td data-label="" editable="true" datatype="inbound"><a href="//${subd.RootOrMatchingDomain}" target="_blank">${subd.RootOrMatchingDomain}</a> ${inboundTlsIcon}</td>
<td data-label="" editable="true" datatype="inbound">
<a href="//${subd.RootOrMatchingDomain}" target="_blank">${subd.RootOrMatchingDomain}</a> ${inboundTlsIcon}<br>
<small class="accessRuleNameUnderHost" ruleid="${subd.AccessFilterUUID}"></small>
</td>
<td data-label="" editable="true" datatype="domain">${subd.Domain} ${tlsIcon}</td>
<td data-label="" editable="true" datatype="vdir">${vdList}</td>
<td data-label="" editable="true" datatype="basicauth">${subd.RequireBasicAuth?`<i class="ui green check icon"></i>`:`<i class="ui grey remove icon"></i>`}</td>
<td data-label="" editable="true" datatype="basicauth">
${subd.RequireBasicAuth?`<i class="ui green check icon"></i>`:`<i class="ui grey remove icon"></i>`}
</td>
<td class="center aligned" editable="true" datatype="action" data-label="">
<div class="ui toggle tiny fitted checkbox" style="margin-bottom: -0.5em; margin-right: 0.4em;" title="Enable / Disable Rule">
<input type="checkbox" class="enableToggle" name="active" ${enableChecked} eptuuid="${subd.RootOrMatchingDomain}" onchange="handleProxyRuleToggle(this);">
@@ -94,9 +101,65 @@
</tr>`);
});
}
resolveAccessRuleNameOnHostRPlist();
});
}
//Resolve & Update all rule names on host PR list
function resolveAccessRuleNameOnHostRPlist(){
//Resolve the access filters
$.get("/api/access/list", function(data){
console.log(data);
if (data.error == undefined){
//Build a map base on the data
let accessRuleMap = {};
for (var i = 0; i < data.length; i++){
accessRuleMap[data[i].ID] = data[i];
}
$(".accessRuleNameUnderHost").each(function(){
let thisAccessRuleID = $(this).attr("ruleid");
if (thisAccessRuleID== ""){
thisAccessRuleID = "default"
}
if (thisAccessRuleID == "default"){
//No need to label default access rules
$(this).html("");
return;
}
let rule = accessRuleMap[thisAccessRuleID];
let icon = `<i class="ui grey filter icon"></i>`;
if (rule.ID == "default"){
icon = `<i class="ui yellow star icon"></i>`;
}else if (rule.BlacklistEnabled && !rule.WhitelistEnabled){
//This is a blacklist filter
icon = `<i class="ui red filter icon"></i>`;
}else if (rule.WhitelistEnabled && !rule.BlacklistEnabled){
//This is a whitelist filter
icon = `<i class="ui green filter icon"></i>`;
}else if (rule.WhitelistEnabled && rule.BlacklistEnabled){
//Whitelist and blacklist filter
icon = `<i class="ui yellow filter icon"></i>`;
}
if (rule != undefined){
$(this).html(`${icon} ${rule.Name}`);
}
});
}
})
}
//Update the access rule name on given epuuid, call by hostAccessEditor.html
function updateAccessRuleNameUnderHost(epuuid, newruleUID){
$(`tr[eptuuid='${epuuid}'].subdEntry`).find(".accessRuleNameUnderHost").attr("ruleid", newruleUID);
resolveAccessRuleNameOnHostRPlist();
}
/*
Inline editor for httprp.html
@@ -176,7 +239,7 @@
column.empty().append(`<div class="ui checkbox" style="margin-top: 0.4em;">
<input type="checkbox" class="RequireBasicAuth" ${checkstate}>
<label>Require Basic Auth</label>
<label>Require Basic Auth</label>
</div>
<button class="ui basic tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editBasicAuthCredentials('${uuid}');"><i class="ui blue user circle icon"></i> Edit Credentials</button>
<div class="ui basic advance segment" style="padding: 0.4em !important; border-radius: 0.4em;">
@@ -191,6 +254,7 @@
<label>Skip WebSocket Origin Check<br>
<small>Check this to allow cross-origin websocket requests</small></label>
</div>
<br>
<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editCustomHeaders('${uuid}');"><i class="heading icon"></i> Custom Headers</button>
<!-- <button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editLoadBalanceOptions('${uuid}');"><i class="blue server icon"></i> Load Balance</button> -->
</div>
@@ -213,7 +277,10 @@
<label>Allow plain HTTP access<br>
<small>Allow inbound connections without TLS/SSL</small></label>
</div><br>
<button class="ui basic tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editAccessRule('${uuid}');"><i class="ui filter icon"></i> Edit Access Rule</button>
`);
$(".hostAccessRuleSelector").dropdown();
}else{
//Unknown field. Leave it untouched
}
@@ -277,6 +344,14 @@
showSideWrapper("snippet/basicAuthEditor.html?t=" + Date.now() + "#" + payload);
}
function editAccessRule(uuid){
let payload = encodeURIComponent(JSON.stringify({
ept: "host",
ep: uuid
}));
showSideWrapper("snippet/hostAccessEditor.html?t=" + Date.now() + "#" + payload);
}
function quickEditVdir(uuid){
openTabById("vdir");
$("#vdirBaseRoutingRule").parent().dropdown("set selected", uuid);
@@ -313,6 +388,9 @@
}
})
}
/* Access List handling */
//Bind on tab switch events

View File

@@ -2,9 +2,6 @@
index.html style overwrite
*/
:root{
--theme_background: linear-gradient(60deg, rgb(84, 58, 183) 0%, rgb(0, 172, 193) 100%);
--theme_background_inverted: linear-gradient(215deg, rgba(38,60,71,1) 13%, rgba(2,3,42,1) 84%);
--theme_green: linear-gradient(270deg, #27e7ff, #00ca52);
@@ -256,7 +253,7 @@ body{
.sideWrapperMenu{
height: 3px;
background-color: #414141;
background: var(--theme_background);
}
/*

View File

@@ -0,0 +1,267 @@
<!DOCTYPE html>
<html>
<head>
<!-- Notes: This should be open in its original path-->
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
<script src="../script/jquery-3.6.0.min.js"></script>
<script src="../script/semantic/semantic.min.js"></script>
<style>
#refreshAccessRuleListBtn{
position: absolute;
top: 0.4em;
right: 1em;
}
</style>
</head>
<body>
<br>
<div class="ui container">
<div class="ui header">
<div class="content">
Access Rule Editor
<div class="sub header">Create, Edit or Remove Access Rules</div>
</div>
</div>
<div class="ui divider"></div>
<div class="ui top attached tabular menu">
<a class="active item" data-tab="new"><i class="ui green add icon"></i> New</a>
<a class="item" data-tab="edit"><i class="ui grey edit icon"></i> Edit</a>
</div>
<div class="ui bottom attached active tab segment" data-tab="new">
<p>Create a new Access Rule</p>
<form class="ui form" id="accessRuleForm">
<div class="field">
<label>Rule Name</label>
<input type="text" name="accessRuleName" placeholder="Rule Name" required>
</div>
<div class="field">
<label>Description</label>
<textarea name="description" placeholder="Description" required></textarea>
</div>
<button class="ui basic button" type="submit"><i class="ui green add icon"></i> Create</button>
</form>
<br>
</div>
<div class="ui bottom attached tab segment" data-tab="edit">
<p>Select an Access Rule to edit</p>
<button id="refreshAccessRuleListBtn" class="ui circular basic icon button" onclick="reloadAccessRuleList()"><i class="ui green refresh icon"></i></button>
<div class="ui selection fluid dropdown" id="accessRuleSelector">
<input type="hidden" name="targetAccessRule" value="default">
<i class="dropdown icon"></i>
<div class="default text"></div>
<div class="menu" id="accessRuleList">
<div class="item" data-value="default"><i class="ui yellow star icon"></i> Default</div>
</div>
</div>
<br>
<form class="ui form" id="modifyRuleInfo">
<div class="disabled field">
<label>Rule ID</label>
<input type="text" name="accessRuleUUID">
</div>
<div class="field">
<label>Rule Name</label>
<input type="text" name="accessRuleName" placeholder="Rule Name" required>
</div>
<div class="field">
<label>Description</label>
<textarea name="description" placeholder="Description" required></textarea>
</div>
<button class="ui basic button" type="submit"><i class="ui green save icon"></i> Save Changes</button>
<button class="ui basic button" onclick="removeAccessRule(event);"><i class="ui red trash icon"></i> Remove Rule</button>
</form>
</div>
<br>
<button class="ui basic button" style="float: right;" onclick="parent.hideSideWrapper();"><i class="remove icon"></i> Close</button>
<br><br><br>
</div>
<script>
let accessRuleList = [];
$('.dropdown').dropdown();
$('.menu .item').tab();
function handleCreateNewAccessRule(event) {
event.preventDefault(); // Prevent the default form submission
const formData = new FormData(event.target);
const accessRuleName = formData.get('accessRuleName');
const description = formData.get('description');
console.log('Access Rule Name:', accessRuleName);
console.log('Description:', description);
$("#accessRuleForm input[name='accessRuleName']").val("");
$("#accessRuleForm textarea[name='description']").val("");
$.ajax({
url: "/api/access/create",
method: "POST",
data: {
"name": accessRuleName,
"desc": description
},
success: function(data){
if (data.error != undefined){
parent.msgbox(data.error, false);
}else{
parent.msgbox("Access Rule Created", true);
reloadAccessRuleList();
if (parent != undefined && parent.reloadAccessRules != undefined){
parent.reloadAccessRules();
}
}
}
})
}
//Handle on change of the dropdown selection
function handleSelectEditingAccessRule(){
const selectedValue = document.querySelector('#accessRuleSelector').querySelector('input').value;
console.log('Selected Value:', selectedValue);
//Load the information from list
loadAccessRuleInfoIntoEditFields(selectedValue);
}
//Load the access rules information into the fields
function loadAccessRuleInfoIntoEditFields(targetAccessRuleUUID){
var targetAccessRule = undefined;
for (var i = 0; i < accessRuleList.length; i++){
let thisAccessRule = accessRuleList[i];
if (thisAccessRule.ID == targetAccessRuleUUID){
targetAccessRule = thisAccessRule;
}
}
if (targetAccessRule == undefined){
//Target exists rule no longer exists
return;
}
let accessRuleID = targetAccessRule.ID;
let accessRuleName = targetAccessRule.Name;
let accessRuleDesc = targetAccessRule.Desc;
//Load the information into the form input field
//Load the information into the form input field
document.querySelector('#modifyRuleInfo input[name="accessRuleUUID"]').value = accessRuleID;
document.querySelector('#modifyRuleInfo input[name="accessRuleName"]').value = accessRuleName;
document.querySelector('#modifyRuleInfo textarea[name="description"]').value = accessRuleDesc;
}
//Bind events to modify rule form
document.getElementById('modifyRuleInfo').addEventListener('submit', function(event){
event.preventDefault(); // Prevent the default form submission
const accessRuleUUID = document.querySelector('#modifyRuleInfo input[name="accessRuleUUID"]').value;
const accessRuleName = document.querySelector('#modifyRuleInfo input[name="accessRuleName"]').value;
const description = document.querySelector('#modifyRuleInfo textarea[name="description"]').value;
console.log('Access Rule UUID:', accessRuleUUID);
console.log('Access Rule Name:', accessRuleName);
console.log('Description:', description);
$.ajax({
url: "/api/access/update",
method: "POST",
data: {
"id":accessRuleUUID,
"name":accessRuleName,
"desc":description
},
success: function(data){
if (data.error != undefined){
parent.msgbox(data.error, false);
}else{
parent.msgbox("Access rule updated", true);
initAccessRuleList(function(){
$("#accessRuleSelector").dropdown("set selected", accessRuleUUID);
loadAccessRuleInfoIntoEditFields(accessRuleUUID);
});
if (parent != undefined && parent.reloadAccessRules != undefined){
parent.reloadAccessRules();
}
}
}
})
});
function initAccessRuleList(callback=undefined){
$.get("/api/access/list", function(data){
if (data.error == undefined){
$("#accessRuleList").html("");
data.forEach(function(rule){
let icon = `<i class="ui grey filter icon"></i>`;
if (rule.ID == "default"){
icon = `<i class="ui yellow star icon"></i>`;
}else if (rule.BlacklistEnabled && !rule.WhitelistEnabled){
//This is a blacklist filter
icon = `<i class="ui red filter icon"></i>`;
}else if (rule.WhitelistEnabled && !rule.BlacklistEnabled){
//This is a whitelist filter
icon = `<i class="ui green filter icon"></i>`;
}
$("#accessRuleList").append(`<div class="item" data-value="${rule.ID}">${icon} ${rule.Name}</div>`);
});
accessRuleList = data;
$(".dropdown").dropdown();
if (callback != undefined){
callback();
}
}
})
}
initAccessRuleList(function(){
$("#accessRuleSelector").dropdown("set selected", "default");
loadAccessRuleInfoIntoEditFields("default");
});
function reloadAccessRuleList(){
initAccessRuleList(function(){
$("#accessRuleSelector").dropdown("set selected", "default");
loadAccessRuleInfoIntoEditFields("default");
});
}
function removeAccessRule(event){
event.preventDefault();
event.stopImmediatePropagation();
let accessRuleUUID = $("#modifyRuleInfo input[name='accessRuleUUID']").val();
if (accessRuleUUID == ""){
return;
}
if (accessRuleUUID == "default"){
parent.msgbox("Default access rule cannot be removed", false);
return;
}
let accessRuleName = $("#modifyRuleInfo input[name='accessRuleName']").val();
if (confirm("Confirm removing access rule " + accessRuleName + "?")){
$.ajax({
url: "/api/access/remove",
data: {
"id": accessRuleUUID
},
method: "POST",
success: function(data){
if (data.error != undefined){
parent.msgbox(data.error, false);
}else{
parent.msgbox("Access rule removed", true);
reloadAccessRuleList();
if (parent != undefined && parent.reloadAccessRules != undefined){
parent.reloadAccessRules();
}
}
}
})
}
}
document.getElementById('accessRuleSelector').addEventListener('change', handleSelectEditingAccessRule);
document.getElementById('accessRuleForm').addEventListener('submit', handleCreateNewAccessRule);
</script>
</body>
</html>

View File

@@ -118,6 +118,14 @@
<label>ACME Server URL</label>
<input id="caURL" type="text" placeholder="https://example.com/acme/dictionary">
</div>
<div class="field" id="kidInput" style="display:none;">
<label>EAB Credentials (KID) for current provider</label>
<input id="eab_kid" type="text" placeholder="Leave this field blank to keep the current configuration">
</div>
<div class="field" id="hmacInput" style="display:none;">
<label>EAB HMAC Key for current provider</label>
<input id="eab_hmac" type="text" placeholder="Leave this field blank to keep the current configuration">
</div>
<div class="field" id="skipTLS" style="display:none;">
<div class="ui checkbox">
<input type="checkbox" id="skipTLSCheckbox">
@@ -314,19 +322,88 @@
// Button click event handler for obtaining certificate
$("#obtainButton").click(function() {
$("#obtainButton").addClass("loading").addClass("disabled");
updateCertificateEAB();
obtainCertificate();
});
$("input[name=ca]").on('change', function() {
if(this.value == "Custom ACME Server") {
$("#caInput").show();
$("#kidInput").show();
$("#hmacInput").show();
$("#skipTLS").show();
} else {
} else if (this.value == "ZeroSSL") {
$("#kidInput").show();
$("#hmacInput").show();
} else if (this.value == "Buypass") {
$("#kidInput").show();
$("#hmacInput").show();
}else {
$("#caInput").hide();
$("#skipTLS").hide();
$("#kidInput").hide();
$("#hmacInput").hide();
}
})
// Obtain certificate from API
function updateCertificateEAB() {
var ca = $("#ca").dropdown("get value");
var caURL = "";
if (ca == "Custom ACME Server") {
ca = "custom";
caURL = $("#caURL").val();
}else if(ca == "Buypass") {
caURL = "https://api.buypass.com/acme/directory";
}else if(ca == "ZeroSSL") {
caURL = "https://acme.zerossl.com/v2/DV90";
}
if(caURL == "") {
return;
}
var kid = $("#eab_kid").val();
var hmac = $("#eab_hmac").val();
if(kid == "" || hmac == "") {
return;
}
console.log(caURL + " " + kid + " " + hmac);
$.ajax({
url: "/api/acme/autoRenew/setEAB",
method: "GET",
data: {
acmeDirectoryURL: caURL,
kid: kid,
hmacEncoded: hmac,
},
success: function(response) {
//$("#obtainButton").removeClass("loading").removeClass("disabled");
if (response.error) {
console.log("Error:", response.error);
// Show error message
parent.msgbox(response.error, false, 12000);
} else {
console.log("Certificate EAB updated successfully");
// Show success message
parent.msgbox("Certificate EAB updated successfully");
// Renew the parent certificate list
parent.initManagedDomainCertificateList();
}
},
error: function(error) {
//$("#obtainButton").removeClass("loading").removeClass("disabled");
console.log("Failed to update EAB configuration:", error);
parent.msgbox("Failed to update EAB configuration");
}
});
}
// Obtain certificate from API
function obtainCertificate() {
var domains = $("#domainsInput").val();

View File

@@ -0,0 +1,187 @@
<!DOCTYPE html>
<html>
<head>
<!-- Notes: This should be open in its original path-->
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
<script src="../script/jquery-3.6.0.min.js"></script>
<script src="../script/semantic/semantic.min.js"></script>
<style>
.accessRule{
cursor: pointer;
border-radius: 0.4em !important;
border: 1px solid rgb(233, 233, 233) !important;
}
.accessRule:hover{
background-color: rgb(241, 241, 241) !important;
}
.accessRule.active{
background-color: rgb(241, 241, 241) !important;
}
.accessRule .selected{
position: absolute;
top: 1em;
right: 0.6em;
}
.accessRule:not(.active) .selected{
display:none;
}
#accessRuleList{
padding: 0.6em;
border: 1px solid rgb(228, 228, 228);
border-radius: 0.4em !important;
max-height: calc(100vh - 15em);
min-height: 300px;
overflow-y: auto;
}
</style>
</head>
<body>
<br>
<div class="ui container">
<div class="ui header">
<div class="content">
Host Access Settings
<div class="sub header" id="epname"></div>
</div>
</div>
<div class="ui divider"></div>
<p>Select an access rule to apply blacklist / whitelist filtering</p>
<div id="accessRuleList">
<div class="ui segment accessRule">
<div class="ui header">
<i class="filter icon"></i>
<div class="content">
Account Settings
<div class="sub header">Manage your preferences</div>
</div>
</div>
</div>
</div>
<br>
<button class="ui basic button" onclick="applyChangeAndClose()"><i class="ui green check icon"></i> Apply Change</button>
<button class="ui basic button" style="float: right;" onclick="parent.hideSideWrapper();"><i class="remove icon"></i> Close</button>
<br><br><br>
</div>
<script>
let editingEndpoint = {};
if (window.location.hash.length > 1){
let payloadHash = window.location.hash.substr(1);
try{
payloadHash = JSON.parse(decodeURIComponent(payloadHash));
$("#epname").text(payloadHash.ep);
editingEndpoint = payloadHash;
}catch(ex){
console.log("Unable to load endpoint data from hash")
}
}
function initAccessRuleList(callback = undefined){
$("#accessRuleList").html("<small>Loading</small>");
$.get("/api/access/list", function(data){
if (data.error == undefined){
$("#accessRuleList").html("");
data.forEach(function(rule){
let icon = `<i class="ui grey filter icon"></i>`;
if (rule.ID == "default"){
icon = `<i class="ui yellow star icon"></i>`;
}else if (rule.BlacklistEnabled && !rule.WhitelistEnabled){
//This is a blacklist filter
icon = `<i class="ui red filter icon"></i>`;
}else if (rule.WhitelistEnabled && !rule.BlacklistEnabled){
//This is a whitelist filter
icon = `<i class="ui green filter icon"></i>`;
}else if (rule.WhitelistEnabled && rule.BlacklistEnabled){
//Whitelist and blacklist filter
icon = `<i class="ui yellow filter icon"></i>`;
}
$("#accessRuleList").append(`<div class="ui basic segment accessRule" ruleid="${rule.ID}" onclick="selectThisRule(this);">
<h5 class="ui header">
${icon}
<div class="content">
${rule.Name}
<div class="sub header">${rule.ID}</div>
</div>
</h5>
<p>${rule.Desc}</p>
${rule.BlacklistEnabled?`<small><i class="ui red filter icon"></i> Blacklist Enabled</small>`:""}
${rule.WhitelistEnabled?`<small><i class="ui green filter icon"></i> Whitelist Enabled</small>`:""}
<div class="selected"><i class="ui large green check icon"></i></div>
</div>`);
});
accessRuleList = data;
$(".dropdown").dropdown();
if (callback != undefined){
callback();
}
}
});
}
initAccessRuleList(function(){
$.ajax({
url: "/api/proxy/detail",
method: "POST",
data: {"type":"host", "epname": editingEndpoint.ep },
success: function(data){
console.log(data);
if (data.error != undefined){
alert(data.error);
}else{
let currentAccessFilter = data.AccessFilterUUID;
if (currentAccessFilter == ""){
//Use default
currentAccessFilter = "default";
}
$(`.accessRule[ruleid=${currentAccessFilter}]`).addClass("active");
}
}
})
});
function selectThisRule(accessRuleObject){
let accessRuleID = $(accessRuleObject).attr("ruleid");
$(".accessRule").removeClass('active');
$(accessRuleObject).addClass('active');
}
function applyChangeAndClose(){
let newAccessRuleID = $(".accessRule.active").attr("ruleid");
let targetEndpoint = editingEndpoint.ep;
$.ajax({
url: "/api/access/attach",
method: "POST",
data: {
id: newAccessRuleID,
host: targetEndpoint
},
success: function(data){
if (data.error != undefined){
parent.msgbox(data.error, false);
}else{
parent.msgbox("Access Rule Updated");
//Modify the parent list if exists
if (parent != undefined && parent.updateAccessRuleNameUnderHost){
parent.updateAccessRuleNameUnderHost(targetEndpoint, newAccessRuleID);
}
parent.hideSideWrapper();
}
}
})
}
</script>
</body>
</html>