parent
c9d13320d5
commit
e60f58a975
|
@ -2,3 +2,4 @@
|
||||||
/node_modules
|
/node_modules
|
||||||
/data
|
/data
|
||||||
*.code-workspace
|
*.code-workspace
|
||||||
|
.vscode/settings.json
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
const { PrismaClient } = require("@prisma/client");
|
const { PrismaClient } = require("@prisma/client");
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
const core = require("./core");
|
const core = require("./core");
|
||||||
|
const settings = require("../settings");
|
||||||
// Check if user registration is allowed via the settings
|
|
||||||
function registration() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function userRegistration(username, password) {
|
async function userRegistration(username, password) {
|
||||||
if (!username) return { success: false, message: "No username provided" };
|
if (!username) return { success: false, message: "No username provided" };
|
||||||
|
@ -29,4 +25,4 @@ async function userLogin(username, password) {
|
||||||
return { success: true, data: existing_user.data };
|
return { success: true, data: existing_user.data };
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { registration, userRegistration, userLogin };
|
module.exports = { userRegistration, userLogin };
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
const validate = require("./form_validation");
|
const validate = require("./form_validation");
|
||||||
const core = require("./core");
|
const core = require("./core");
|
||||||
|
const settings = require("../settings");
|
||||||
|
|
||||||
async function registerUser(username, password) {
|
async function registerUser(username, password) {
|
||||||
const registration_allowed = validate.registration(); // Check if user registration is allowed
|
const registration_allowed = await settings.userRegistrationAllowed(); // Check if user registration is allowed
|
||||||
const form_valid = await validate.userRegistration(username, password); // Check form for errors
|
const form_valid = await validate.userRegistration(username, password); // Check form for errors
|
||||||
|
|
||||||
|
const is_setup_complete = await settings.setupComplete();
|
||||||
|
let role = is_setup_complete ? "ADMIN" : null;
|
||||||
|
|
||||||
// Register the user in the database
|
// Register the user in the database
|
||||||
if (registration_allowed && form_valid.success) return await core.registerUser(username, password);
|
if (registration_allowed && form_valid.success) return await core.registerUser(username, password, { role: role });
|
||||||
|
|
||||||
// Something went wrong!
|
// Something went wrong!
|
||||||
return { success: false, message: form_valid.message };
|
return { success: false, message: form_valid.message };
|
||||||
|
@ -19,4 +23,15 @@ async function loginUser(username, password) {
|
||||||
return { success: true, data: { username: user.data.username, id: user.data.id, password: user.data.password } };
|
return { success: true, data: { username: user.data.username, id: user.data.id, password: user.data.password } };
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { registerUser, loginUser };
|
async function getUser({ id, username } = {}) {
|
||||||
|
let user;
|
||||||
|
if (id) user = await core.getUser({ id: id });
|
||||||
|
else if (username) user = await core.getUser({ username: username });
|
||||||
|
|
||||||
|
// Make sure we only get important identifier and nothing sensitive!
|
||||||
|
if (user.success) return { success: true, data: { username: user.data.username, id: user.data.id, role: user.data.role } };
|
||||||
|
|
||||||
|
return { success: false, message: "No user found" };
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { registerUser, loginUser, getUser };
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
const internal = require("./core/internal_api");
|
const internal = require("./core/internal_api");
|
||||||
const bcrypt = require("bcrypt");
|
const bcrypt = require("bcrypt");
|
||||||
const persistent_setting = require("node-persist");
|
const settings = require("./settings");
|
||||||
persistent_setting.init({ dir: "data/" });
|
|
||||||
|
|
||||||
async function index(request, response) {
|
async function index(request, response) {
|
||||||
// Check if the master admin has been created
|
// Check if the master admin has been created
|
||||||
const is_setup_complete = (await persistent_setting.getItem("SETUP_COMPLETE")) || false;
|
const is_setup_complete = (await settings.setupComplete()) || false;
|
||||||
if (!is_setup_complete) return response.redirect("/register");
|
if (!is_setup_complete) return response.redirect("/register");
|
||||||
|
|
||||||
response.render("index.ejs", { website_name: process.env.WEBSITE_NAME });
|
response.render("index.ejs", { website_name: process.env.WEBSITE_NAME });
|
||||||
|
@ -17,6 +16,13 @@ function register(request, response) {
|
||||||
function login(request, response) {
|
function login(request, response) {
|
||||||
response.render("login.ejs", { website_name: process.env.WEBSITE_NAME });
|
response.render("login.ejs", { website_name: process.env.WEBSITE_NAME });
|
||||||
}
|
}
|
||||||
|
async function admin(request, response) {
|
||||||
|
const reg_allowed = await settings.userRegistrationAllowed();
|
||||||
|
response.render("admin.ejs", {
|
||||||
|
website_name: process.env.WEBSITE_NAME,
|
||||||
|
settings: { registration_enabled: reg_allowed },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function registerPost(request, response) {
|
async function registerPost(request, response) {
|
||||||
const hashedPassword = await bcrypt.hash(request.body.password, 10); // Hash the password for security :^)
|
const hashedPassword = await bcrypt.hash(request.body.password, 10); // Hash the password for security :^)
|
||||||
|
@ -31,4 +37,15 @@ async function loginPost(request, response) {
|
||||||
request.session.user = { username: login.data.username, id: login.data.id };
|
request.session.user = { username: login.data.username, id: login.data.id };
|
||||||
response.json({ success: true });
|
response.json({ success: true });
|
||||||
}
|
}
|
||||||
module.exports = { index, register, login, registerPost, loginPost };
|
|
||||||
|
async function settingPost(request, response) {
|
||||||
|
const user = await internal.getUser({ id: request.session.user.id });
|
||||||
|
|
||||||
|
if (!user.success) return response.json({ success: false, message: user.message });
|
||||||
|
if (user.data.role !== "ADMIN") return response.json({ success: false, message: "User is not permitted" });
|
||||||
|
|
||||||
|
if (request.body.setting_name === "ACCOUNT_REGISTRATION") settings.setUserRegistrationAllowed(request.body.value);
|
||||||
|
|
||||||
|
response.json({ success: true });
|
||||||
|
}
|
||||||
|
module.exports = { index, register, login, admin, registerPost, loginPost, settingPost };
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
const persistent_setting = require("node-persist");
|
||||||
|
persistent_setting.init({ dir: "data/" });
|
||||||
|
|
||||||
|
const setupComplete = async () => (await persistent_setting.getItem("SETUP_COMPLETE")) || false;
|
||||||
|
const userRegistrationAllowed = async () => (await persistent_setting.getItem("REGISTRATION_ALLOWED")) == "true";
|
||||||
|
|
||||||
|
const setUserRegistrationAllowed = (new_value) => persistent_setting.setItem("REGISTRATION_ALLOWED", String(new_value));
|
||||||
|
|
||||||
|
module.exports = { setupComplete, userRegistrationAllowed, setUserRegistrationAllowed };
|
|
@ -0,0 +1,133 @@
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background-color: #111;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
background-color: #222;
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 0 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
.header .left {
|
||||||
|
margin: auto auto auto 0;
|
||||||
|
font-size: 20px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.header .left a {
|
||||||
|
width: inherit;
|
||||||
|
}
|
||||||
|
.header .right {
|
||||||
|
margin: auto 0 auto auto;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
.header .right a:hover,
|
||||||
|
.header .right a:focus {
|
||||||
|
background-color: #333;
|
||||||
|
}
|
||||||
|
.header a {
|
||||||
|
height: 100%;
|
||||||
|
width: 130px;
|
||||||
|
margin: auto 0;
|
||||||
|
display: flex;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background-color ease-in-out 0.1s;
|
||||||
|
}
|
||||||
|
.header a div {
|
||||||
|
margin: auto;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: #1c478a;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover,
|
||||||
|
button:focus {
|
||||||
|
background-color: #122d57;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.good {
|
||||||
|
background-color: #015b01;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.bad {
|
||||||
|
background-color: #8e0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
width: 1000px;
|
||||||
|
min-height: 10px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.page .horizontal-button-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
.page .horizontal-button-container button {
|
||||||
|
width: 100px;
|
||||||
|
min-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1010px) {
|
||||||
|
.page {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
background-color: #222;
|
||||||
|
min-height: 10px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.container .settings-group .settings-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.container .settings-group .settings-row .label {
|
||||||
|
margin: auto auto auto 0;
|
||||||
|
}
|
||||||
|
.container .settings-group .settings-row .button-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin: auto 0 auto auto;
|
||||||
|
}
|
||||||
|
.container .settings-group .settings-row .button-group .spinner {
|
||||||
|
margin: auto 10px auto auto;
|
||||||
|
animation: spin 1s;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
.container .settings-group .settings-row .button-group button {
|
||||||
|
height: 30px;
|
||||||
|
padding: 0px 20px;
|
||||||
|
}
|
||||||
|
.container .settings-group .settings-row:nth-child(even) {
|
||||||
|
background-color: rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(-360deg);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
@use "theme";
|
||||||
|
|
||||||
|
.container {
|
||||||
|
background-color: theme.$header-color;
|
||||||
|
min-height: 10px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.settings-group {
|
||||||
|
.settings-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
.label {
|
||||||
|
margin: auto auto auto 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin: auto 0 auto auto;
|
||||||
|
.spinner {
|
||||||
|
margin: auto 10px auto auto;
|
||||||
|
animation: spin 1s;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
height: 30px;
|
||||||
|
padding: 0px 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.settings-row:nth-child(even) {
|
||||||
|
background-color: #00000033;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(-360deg);
|
||||||
|
}
|
||||||
|
}
|
|
@ -58,6 +58,14 @@ button:focus {
|
||||||
background-color: #122d57;
|
background-color: #122d57;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.good {
|
||||||
|
background-color: #015b01;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.bad {
|
||||||
|
background-color: #8e0000;
|
||||||
|
}
|
||||||
|
|
||||||
.page {
|
.page {
|
||||||
width: 1000px;
|
width: 1000px;
|
||||||
min-height: 10px;
|
min-height: 10px;
|
||||||
|
@ -72,6 +80,10 @@ button:focus {
|
||||||
min-height: 30px;
|
min-height: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 1010px) {
|
@media screen and (max-width: 1010px) {
|
||||||
.page {
|
.page {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -65,6 +65,13 @@ button:focus {
|
||||||
background-color: #122d57;
|
background-color: #122d57;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.good {
|
||||||
|
background-color: #015b01;
|
||||||
|
}
|
||||||
|
button.bad {
|
||||||
|
background-color: #8e0000;
|
||||||
|
}
|
||||||
|
|
||||||
.page {
|
.page {
|
||||||
width: 1000px;
|
width: 1000px;
|
||||||
min-height: 10px;
|
min-height: 10px;
|
||||||
|
@ -81,6 +88,9 @@ button:focus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
@media screen and (max-width: 1010px) {
|
@media screen and (max-width: 1010px) {
|
||||||
.page {
|
.page {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
async function toggleState(setting_name, new_value, element_id) {
|
||||||
|
// Show spinner
|
||||||
|
qs(`#${element_id}`).parentNode.querySelector(".spinner").classList.remove("hidden");
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
const form = new FormData();
|
||||||
|
form.append("setting_name", setting_name);
|
||||||
|
form.append("value", new_value);
|
||||||
|
|
||||||
|
const response = await request("/setting", "POST", form);
|
||||||
|
|
||||||
|
// Check response for errors
|
||||||
|
if (response.body.success) {
|
||||||
|
qs(`#${element_id}`).parentNode.querySelector(".spinner").classList.add("hidden");
|
||||||
|
|
||||||
|
// Update visual to reflect current setting
|
||||||
|
// Class
|
||||||
|
const add_class = new_value ? "good" : "bad";
|
||||||
|
const remove_class = new_value ? "bad" : "good";
|
||||||
|
qs(`#${element_id}`).classList.remove(remove_class);
|
||||||
|
qs(`#${element_id}`).classList.add(add_class);
|
||||||
|
|
||||||
|
// Text
|
||||||
|
const new_text = new_value ? "Enabled" : "Disabled";
|
||||||
|
qs(`#${element_id}`).children[0].innerText = new_text;
|
||||||
|
|
||||||
|
// Function
|
||||||
|
const add_function = new_value ? "toggleState('ACCOUNT_REGISTRATION', false, this.id)" : "toggleState('ACCOUNT_REGISTRATION', true, this.id)";
|
||||||
|
qs(`#${element_id}`).removeAttribute("onclick");
|
||||||
|
qs(`#${element_id}`).setAttribute("onclick", add_function);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/admin.css" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/theme.css" />
|
||||||
|
<script src="/js/generic.js"></script>
|
||||||
|
<title><%= website_name %> | Administration</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<%- include("partials/header.ejs", {selected: 'home'}) %>
|
||||||
|
|
||||||
|
<div class="page">
|
||||||
|
<div class="container">
|
||||||
|
<div class="settings-group">
|
||||||
|
<h1>Accounts</h1>
|
||||||
|
<div class="settings-row">
|
||||||
|
<div class="label">Account registration</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<div class="spinner hidden">
|
||||||
|
<div>↺</div>
|
||||||
|
</div>
|
||||||
|
<% if (!settings.registration_enabled ) { %>
|
||||||
|
<button id="registration_toggle" onclick="toggleState('ACCOUNT_REGISTRATION', true, this.id)" class="bad"><div>Disabled</div></button>
|
||||||
|
<% } else { %>
|
||||||
|
<button id="registration_toggle" onclick="toggleState('ACCOUNT_REGISTRATION', false, this.id)" class="good"><div>Enabled</div></button>
|
||||||
|
<%}%>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%- include("partials/footer.ejs") %>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
<script defer src="/js/admin.js"></script>
|
2
yab.js
2
yab.js
|
@ -42,6 +42,7 @@ app.get("/register", checkNotAuthenticated, page_scripts.register);
|
||||||
app.post("/register", checkNotAuthenticated, page_scripts.registerPost);
|
app.post("/register", checkNotAuthenticated, page_scripts.registerPost);
|
||||||
|
|
||||||
// Account Required Endpoints
|
// Account Required Endpoints
|
||||||
|
app.post("/setting", checkAuthenticated, page_scripts.settingPost);
|
||||||
// app.get("/blog/new", checkAuthenticated, page_scripts.blogNew);
|
// app.get("/blog/new", checkAuthenticated, page_scripts.blogNew);
|
||||||
// app.post("/blog", checkAuthenticated, page_scripts.postBlog);
|
// app.post("/blog", checkAuthenticated, page_scripts.postBlog);
|
||||||
// app.delete("/logout", page_scripts.logout);
|
// app.delete("/logout", page_scripts.logout);
|
||||||
|
@ -51,6 +52,7 @@ app.post("/register", checkNotAuthenticated, page_scripts.registerPost);
|
||||||
|
|
||||||
// Endpoints
|
// Endpoints
|
||||||
app.get("/", page_scripts.index);
|
app.get("/", page_scripts.index);
|
||||||
|
app.get("/admin", checkAuthenticated, page_scripts.admin);
|
||||||
// app.get("/blog", page_scripts.blogList);
|
// app.get("/blog", page_scripts.blogList);
|
||||||
// app.get("/blog/:id", page_scripts.blogSingle);
|
// app.get("/blog/:id", page_scripts.blogSingle);
|
||||||
// app.get("/projects", page_scripts.projectList);
|
// app.get("/projects", page_scripts.projectList);
|
||||||
|
|
Loading…
Reference in New Issue