Add the possibility to still configure reverse proxy to web UI even if an admin user already exists

This commit is contained in:
Théophile Diot 2024-04-04 23:00:13 +01:00
parent 5ed0813c65
commit 33ad4c5f29
No known key found for this signature in database
GPG key ID: 248FEA4BAE400D06
2 changed files with 134 additions and 115 deletions

View file

@ -412,7 +412,7 @@ def set_csp_header(response):
+ " style-src 'self' 'unsafe-inline';"
+ " img-src 'self' data: https://assets.bunkerity.com;"
+ " font-src 'self' data:;"
+ (" connect-src *;" if not app.config["USER"] else "")
+ " connect-src *;"
)
return response
@ -503,31 +503,34 @@ def setup():
return redirect(url_for("home"))
db_config = app.config["CONFIG"].get_config(methods=False)
db_user = db.get_ui_user()
for server_name in db_config["SERVER_NAME"].split(" "):
if db_config.get(f"{server_name}_USE_UI", "no") == "yes":
return redirect(url_for("login"), 301)
if request.method == "POST":
is_request_form("setup")
if not any(key in request.form for key in ("admin_username", "admin_password", "admin_password_check", "server_name", "ui_host", "ui_url")):
return redirect_flash_error(
"Missing either admin_username, admin_password, admin_password_check, server_name, ui_host, ui_url or auto_lets_encrypt parameter.", "setup"
)
required_keys = ["server_name", "ui_host", "ui_url"]
if not db_user:
required_keys.extend(["admin_username", "admin_password", "admin_password_check"])
if len(request.form["admin_username"]) > 256:
return redirect_flash_error("The admin username is too long. It must be less than 256 characters.", "setup")
if not any(key in request.form for key in required_keys):
return redirect_flash_error(f"Missing either one of the following parameters: {', '.join(required_keys)}.", "setup")
if request.form["admin_password"] != request.form["admin_password_check"]:
return redirect_flash_error("The passwords do not match.", "setup")
if not db_user:
if len(request.form["admin_username"]) > 256:
return redirect_flash_error("The admin username is too long. It must be less than 256 characters.", "setup")
if not USER_PASSWORD_RX.match(request.form["admin_password"]):
return redirect_flash_error(
"The admin password is not strong enough. It must contain at least 8 characters, including at least 1 uppercase letter, 1 lowercase letter, 1 number and 1 special character (#@?!$%^&*-).",
"setup",
)
if request.form["admin_password"] != request.form["admin_password_check"]:
return redirect_flash_error("The passwords do not match.", "setup")
if not USER_PASSWORD_RX.match(request.form["admin_password"]):
return redirect_flash_error(
"The admin password is not strong enough. It must contain at least 8 characters, including at least 1 uppercase letter, 1 lowercase letter, 1 number and 1 special character (#@?!$%^&*-).",
"setup",
)
server_names = db_config["SERVER_NAME"].split(" ")
if request.form["server_name"] in server_names:
@ -540,13 +543,14 @@ def setup():
if not REVERSE_PROXY_PATH.match(request.form["ui_host"]):
return redirect_flash_error("The hostname is not valid.", "setup")
app.config["USER"] = User(request.form["admin_username"], request.form["admin_password"], method="ui")
if not db_user:
app.config["USER"] = User(request.form["admin_username"], request.form["admin_password"], method="ui")
ret = db.create_ui_user(request.form["admin_username"], app.config["USER"].password_hash, method="ui")
if ret:
return redirect_flash_error(f"Couldn't create the admin user in the database: {ret}", "setup", False, "error")
ret = db.create_ui_user(request.form["admin_username"], app.config["USER"].password_hash, method="ui")
if ret:
return redirect_flash_error(f"Couldn't create the admin user in the database: {ret}", "setup", False, "error")
flash("The admin user was created successfully", "success")
flash("The admin user was created successfully", "success")
app.config["RELOADING"] = True
app.config["LAST_RELOAD"] = time()
@ -575,6 +579,7 @@ def setup():
return render_template(
"setup.html",
ui_user=db_user,
username=getenv("ADMIN_USERNAME", ""),
password=getenv("ADMIN_PASSWORD", ""),
ui_host=db_config.get("UI_HOST", getenv("UI_HOST", "")),

View file

@ -121,58 +121,62 @@
name="next"
value="{{ request.values.get('next', '') }}" />
<h2 class="col-span-12 block text-left font-bold my-4 text-2xl">Account</h2>
<!-- username inpt-->
<div class="flex flex-col relative col-span-12 my-3 mx-2 max-w-[400px] w-full">
<h5 class="text-base my-1 transition duration-300 ease-in-out text-md font-bold m-0">
Username<strong class="required-mark">*</strong>
</h5>
<label class="sr-only" for="admin_username">Username</label>
<input type="text"
id="admin_username"
name="admin_username"
class="col-span-12 disabled:opacity-75 focus:valid:border-green-500 focus:invalid:border-red-500 outline-none focus:border-primary text-sm leading-5.6 ease block w-full appearance-none rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-4 py-2 font-normal text-gray-700 transition-all placeholder:text-gray-500"
placeholder="enter username"
value="{{ username }}"
pattern="(.*?)"
maxlength="256"
required />
</div>
<!-- end username inpt-->
<!-- password inpt-->
<div class="flex flex-col relative col-span-12 my-3 mx-2 max-w-[400px] w-full">
<h5 class="text-base my-1 transition duration-300 ease-in-out text-md font-bold m-0">
Password<strong class="required-mark">*</strong>
</h5>
<label class="sr-only" for="admin_password">Password</label>
<input type="password"
id="admin_password"
name="admin_password"
class="col-span-12 disabled:opacity-75 focus:valid:border-green-500 focus:invalid:border-red-500 outline-none focus:border-primary text-sm leading-5.6 ease block w-full appearance-none rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-4 py-2 font-normal text-gray-700 transition-all placeholder:text-gray-500"
placeholder="enter password"
value="{{ password }}"
pattern="^(?=.*?\d)(?=.*?[ !\u0022#$%&'\(\)*+,.\/:;<=>?@\[\\\]^_`\u007B\u007C\u007D\u007E\u002D]).{8,}$"
minlength="8"
required />
</div>
<!-- end password inpt-->
<!-- password inpt-->
<div class="flex flex-col relative col-span-12 my-3 mx-2 max-w-[400px] w-full">
<h5 class="text-base my-1 transition duration-300 ease-in-out text-md font-bold m-0">
Confirm Password<strong class="required-mark">*</strong>
</h5>
<label class="sr-only" for="admin_password_check">Confirm Password</label>
<input type="password"
id="admin_password_check"
name="admin_password_check"
class="col-span-12 disabled:opacity-75 focus:valid:border-green-500 focus:invalid:border-red-500 outline-none focus:border-primary text-sm leading-5.6 ease block w-full appearance-none rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-4 py-2 font-normal text-gray-700 transition-all placeholder:text-gray-500"
placeholder="confirm password"
value="{{ password }}"
pattern="^(?=.*?\d)(?=.*?[ !\u0022#$%&'\(\)*+,.\/:;<=>?@\[\\\]^_`\u007B\u007C\u007D\u007E\u002D]).{8,}$"
minlength="8"
required />
<strong class="opacity-0 font-normal text-sm text-red-500" data-pw-alert>Value does not match password</strong>
</div>
<!-- end password inpt-->
{% if not ui_user %}
<!-- username inpt-->
<div class="flex flex-col relative col-span-12 my-3 mx-2 max-w-[400px] w-full">
<h5 class="text-base my-1 transition duration-300 ease-in-out text-md font-bold m-0">
Username<strong class="required-mark">*</strong>
</h5>
<label class="sr-only" for="admin_username">Username</label>
<input type="text"
id="admin_username"
name="admin_username"
class="col-span-12 disabled:opacity-75 focus:valid:border-green-500 focus:invalid:border-red-500 outline-none focus:border-primary text-sm leading-5.6 ease block w-full appearance-none rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-4 py-2 font-normal text-gray-700 transition-all placeholder:text-gray-500"
placeholder="enter username"
value="{{ username }}"
pattern="(.*?)"
maxlength="256"
required />
</div>
<!-- end username inpt-->
<!-- password inpt-->
<div class="flex flex-col relative col-span-12 my-3 mx-2 max-w-[400px] w-full">
<h5 class="text-base my-1 transition duration-300 ease-in-out text-md font-bold m-0">
Password<strong class="required-mark">*</strong>
</h5>
<label class="sr-only" for="admin_password">Password</label>
<input type="password"
id="admin_password"
name="admin_password"
class="col-span-12 disabled:opacity-75 focus:valid:border-green-500 focus:invalid:border-red-500 outline-none focus:border-primary text-sm leading-5.6 ease block w-full appearance-none rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-4 py-2 font-normal text-gray-700 transition-all placeholder:text-gray-500"
placeholder="enter password"
value="{{ password }}"
pattern="^(?=.*?\d)(?=.*?[ !\u0022#$%&'\(\)*+,.\/:;<=>?@\[\\\]^_`\u007B\u007C\u007D\u007E\u002D]).{8,}$"
minlength="8"
required />
</div>
<!-- end password inpt-->
<!-- password inpt-->
<div class="flex flex-col relative col-span-12 my-3 mx-2 max-w-[400px] w-full">
<h5 class="text-base my-1 transition duration-300 ease-in-out text-md font-bold m-0">
Confirm Password<strong class="required-mark">*</strong>
</h5>
<label class="sr-only" for="admin_password_check">Confirm Password</label>
<input type="password"
id="admin_password_check"
name="admin_password_check"
class="col-span-12 disabled:opacity-75 focus:valid:border-green-500 focus:invalid:border-red-500 outline-none focus:border-primary text-sm leading-5.6 ease block w-full appearance-none rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-4 py-2 font-normal text-gray-700 transition-all placeholder:text-gray-500"
placeholder="confirm password"
value="{{ password }}"
pattern="^(?=.*?\d)(?=.*?[ !\u0022#$%&'\(\)*+,.\/:;<=>?@\[\\\]^_`\u007B\u007C\u007D\u007E\u002D]).{8,}$"
minlength="8"
required />
<strong class="opacity-0 font-normal text-sm text-red-500" data-pw-alert>Value does not match password</strong>
</div>
<!-- end password inpt-->
{% else %}
<h6 class="col-span-12 block text-left font-bold mb-4 mt-2">🧑‍🚀 An admin user already exists</h6>
{% endif %}
<!-- email inpt-->
<div class="flex flex-col relative col-span-12 mx-2 max-w-[400px] w-full">
<h5 class="text-base mb-1 transition duration-300 ease-in-out text-md font-bold m-0">Email</h5>
@ -511,9 +515,11 @@
class SubmitForm {
constructor() {
this.pwEl = document.querySelector("#admin_password");
this.pwCheckEl = document.querySelector("#admin_password_check");
this.pwAlertEl = document.querySelector("[data-pw-alert]");
{% if not ui_user %}
this.pwEl = document.querySelector("#admin_password");
this.pwCheckEl = document.querySelector("#admin_password_check");
this.pwAlertEl = document.querySelector("[data-pw-alert]");
{% endif %}
this.formEl = document.querySelector("#setup-form");
this.servInp = document.querySelector("#server_name");
this.sslCheck = document.querySelector("#auto_lets_encrypt");
@ -529,15 +535,17 @@
}
init() {
// Check password to send
this.checkSamePW();
this.pwCheckEl.addEventListener("input", (e) => {
{% if not ui_user %}
// Check password to send
this.checkSamePW();
});
this.pwCheckEl.addEventListener("input", (e) => {
this.checkSamePW();
});
this.pwEl.addEventListener("input", (e) => {
this.checkSamePW();
});
this.pwEl.addEventListener("input", (e) => {
this.checkSamePW();
});
{% endif %}
// Case error and display msg
window.addEventListener("click", (e) => {
@ -554,9 +562,11 @@
// Submit
this.formEl.addEventListener("submit", (e) => {
// Case not same PW
if (this.pwEl.value !== this.pwCheckEl.value)
return e.preventDefault();
{% if not ui_user %}
// Case not same PW
if (this.pwEl.value !== this.pwCheckEl.value)
return e.preventDefault();
{% endif %}
e.preventDefault();
@ -622,15 +632,17 @@
document.querySelector('#newsletter-form').submit();
}
checkSamePW() {
if (!this.pwEl.value || !this.pwCheckEl.value)
{% if not ui_user %}
checkSamePW() {
if (!this.pwEl.value || !this.pwCheckEl.value)
return this.hidePWAlert();
if (this.pwEl.value !== this.pwCheckEl.value)
return this.showPWAlert();
return this.hidePWAlert();
if (this.pwEl.value !== this.pwCheckEl.value)
return this.showPWAlert();
return this.hidePWAlert();
}
}
{% endif %}
showErrMsg() {
this.flashMsg.classList.remove("hidden");
@ -642,29 +654,31 @@
this.flashMsg.setAttribute("aria-hidden", "true");
}
hidePWAlert() {
this.pwCheckEl.classList.remove(
"focus:!border-red-500",
"focus:valid:!border-red-500",
"active:!border-red-500",
"active:valid:!border-red-500",
"valid:!border-red-500"
);
this.pwAlertEl.classList.add("opacity-0");
this.pwAlertEl.setAttribute("aria-hidden", "true");
}
{% if not ui_user %}
hidePWAlert() {
this.pwCheckEl.classList.remove(
"focus:!border-red-500",
"focus:valid:!border-red-500",
"active:!border-red-500",
"active:valid:!border-red-500",
"valid:!border-red-500"
);
this.pwAlertEl.classList.add("opacity-0");
this.pwAlertEl.setAttribute("aria-hidden", "true");
}
showPWAlert() {
this.pwCheckEl.classList.add(
"focus:!border-red-500",
"focus:valid:!border-red-500",
"active:!border-red-500",
"active:valid:!border-red-500",
"valid:!border-red-500"
);
this.pwAlertEl.classList.remove("opacity-0");
this.pwAlertEl.setAttribute("aria-hidden", "false");
}
showPWAlert() {
this.pwCheckEl.classList.add(
"focus:!border-red-500",
"focus:valid:!border-red-500",
"active:!border-red-500",
"active:valid:!border-red-500",
"valid:!border-red-500"
);
this.pwAlertEl.classList.remove("opacity-0");
this.pwAlertEl.setAttribute("aria-hidden", "false");
}
{% endif %}
showLoader() {
this.loaderMsg.textContent = "Setting up...";