add create admin page and prompt it when a kutt instance is ran for the first time
This commit is contained in:
parent
8a73c5ec4c
commit
dab1ac4139
@ -4,6 +4,7 @@ const { v4: uuid } = require("uuid");
|
|||||||
const bcrypt = require("bcryptjs");
|
const bcrypt = require("bcryptjs");
|
||||||
const nanoid = require("nanoid");
|
const nanoid = require("nanoid");
|
||||||
|
|
||||||
|
const { ROLES } = require("../consts");
|
||||||
const query = require("../queries");
|
const query = require("../queries");
|
||||||
const utils = require("../utils");
|
const utils = require("../utils");
|
||||||
const redis = require("../redis");
|
const redis = require("../redis");
|
||||||
@ -26,13 +27,12 @@ function authenticate(type, error, isStrict, redirect) {
|
|||||||
(user && isStrict && !user.verified) ||
|
(user && isStrict && !user.verified) ||
|
||||||
(user && user.banned))
|
(user && user.banned))
|
||||||
) {
|
) {
|
||||||
const path = user.banned ? "/logout" : "/login";
|
|
||||||
if (redirect === "page") {
|
if (redirect === "page") {
|
||||||
res.redirect(path);
|
res.redirect("/logout");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (redirect === "header") {
|
if (redirect === "header") {
|
||||||
res.setHeader("HX-Redirect", path);
|
res.setHeader("HX-Redirect", "/logout");
|
||||||
res.send("NOT_AUTHENTICATED");
|
res.send("NOT_AUTHENTICATED");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -125,6 +125,33 @@ async function signup(req, res) {
|
|||||||
return res.status(201).send({ message: "A verification email has been sent." });
|
return res.status(201).send({ message: "A verification email has been sent." });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createAdminUser(req, res) {
|
||||||
|
const isThereAUser = await query.user.findAny();
|
||||||
|
if (isThereAUser) {
|
||||||
|
throw new CustomError("Can not create the admin user because a user already exists.", 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
const salt = await bcrypt.genSalt(12);
|
||||||
|
const password = await bcrypt.hash(req.body.password, salt);
|
||||||
|
|
||||||
|
const user = await query.user.add({
|
||||||
|
email: req.body.email,
|
||||||
|
password,
|
||||||
|
role: ROLES.ADMIN,
|
||||||
|
verified: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const token = utils.signToken(user);
|
||||||
|
|
||||||
|
if (req.isHTML) {
|
||||||
|
utils.setToken(res, token);
|
||||||
|
res.render("partials/auth/welcome");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(201).send({ token });
|
||||||
|
}
|
||||||
|
|
||||||
function login(req, res) {
|
function login(req, res) {
|
||||||
const token = utils.signToken(req.user);
|
const token = utils.signToken(req.user);
|
||||||
|
|
||||||
@ -382,6 +409,7 @@ module.exports = {
|
|||||||
changeEmailRequest,
|
changeEmailRequest,
|
||||||
changePassword,
|
changePassword,
|
||||||
cooldown,
|
cooldown,
|
||||||
|
createAdminUser,
|
||||||
featureAccess,
|
featureAccess,
|
||||||
featureAccessPage,
|
featureAccessPage,
|
||||||
generateApiKey,
|
generateApiKey,
|
||||||
|
@ -3,16 +3,29 @@ const utils = require("../utils");
|
|||||||
const env = require("../env");
|
const env = require("../env");
|
||||||
|
|
||||||
async function homepage(req, res) {
|
async function homepage(req, res) {
|
||||||
|
const isThereAUser = await query.user.findAny();
|
||||||
|
if (!isThereAUser) {
|
||||||
|
res.redirect("/create-admin");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
res.render("homepage", {
|
res.render("homepage", {
|
||||||
title: "Modern open source URL shortener",
|
title: "Modern open source URL shortener",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function login(req, res) {
|
async function login(req, res) {
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
res.redirect("/");
|
res.redirect("/");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isThereAUser = await query.user.findAny();
|
||||||
|
if (!isThereAUser) {
|
||||||
|
res.redirect("/create-admin");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
res.render("login", {
|
res.render("login", {
|
||||||
title: "Log in or sign up"
|
title: "Log in or sign up"
|
||||||
});
|
});
|
||||||
@ -25,6 +38,17 @@ function logout(req, res) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createAdmin(req, res) {
|
||||||
|
const isThereAUser = await query.user.findAny();
|
||||||
|
if (isThereAUser) {
|
||||||
|
res.redirect("/login");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.render("create_admin", {
|
||||||
|
title: "Create admin account"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function notFound(req, res) {
|
function notFound(req, res) {
|
||||||
res.render("404", {
|
res.render("404", {
|
||||||
title: "404 - Not found"
|
title: "404 - Not found"
|
||||||
@ -266,6 +290,7 @@ module.exports = {
|
|||||||
confirmLinkDelete,
|
confirmLinkDelete,
|
||||||
confirmUserBan,
|
confirmUserBan,
|
||||||
confirmUserDelete,
|
confirmUserDelete,
|
||||||
|
createAdmin,
|
||||||
createUser,
|
createUser,
|
||||||
getReportEmail,
|
getReportEmail,
|
||||||
getSupportEmail,
|
getSupportEmail,
|
||||||
|
@ -417,6 +417,19 @@ const login = [
|
|||||||
.withMessage("Email length must be max 255.")
|
.withMessage("Email length must be max 255.")
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const createAdmin = [
|
||||||
|
body("password", "Password is not valid.")
|
||||||
|
.exists({ checkFalsy: true, checkNull: true })
|
||||||
|
.isLength({ min: 8, max: 64 })
|
||||||
|
.withMessage("Password length must be between 8 and 64."),
|
||||||
|
body("email", "Email is not valid.")
|
||||||
|
.exists({ checkFalsy: true, checkNull: true })
|
||||||
|
.trim()
|
||||||
|
.isEmail()
|
||||||
|
.isLength({ min: 0, max: 255 })
|
||||||
|
.withMessage("Email length must be max 255.")
|
||||||
|
];
|
||||||
|
|
||||||
const changePassword = [
|
const changePassword = [
|
||||||
body("currentpassword", "Password is not valid.")
|
body("currentpassword", "Password is not valid.")
|
||||||
.exists({ checkFalsy: true, checkNull: true })
|
.exists({ checkFalsy: true, checkNull: true })
|
||||||
@ -593,6 +606,7 @@ module.exports = {
|
|||||||
changePassword,
|
changePassword,
|
||||||
checkUser,
|
checkUser,
|
||||||
cooldown,
|
cooldown,
|
||||||
|
createAdmin,
|
||||||
createLink,
|
createLink,
|
||||||
createUser,
|
createUser,
|
||||||
deleteLink,
|
deleteLink,
|
||||||
|
@ -38,6 +38,8 @@ async function add(params, user) {
|
|||||||
const data = {
|
const data = {
|
||||||
email: params.email,
|
email: params.email,
|
||||||
password: params.password,
|
password: params.password,
|
||||||
|
...(params.role && { role: params.role }),
|
||||||
|
...(params.verified !== undefined && { verified: params.verified }),
|
||||||
verification_token: uuid(),
|
verification_token: uuid(),
|
||||||
verification_expires: utils.dateToUTC(addMinutes(new Date(), 60))
|
verification_expires: utils.dateToUTC(addMinutes(new Date(), 60))
|
||||||
};
|
};
|
||||||
@ -216,10 +218,27 @@ async function create(params) {
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if there exists a user
|
||||||
|
async function findAny() {
|
||||||
|
if (env.REDIS_ENABLED) {
|
||||||
|
const anyuser = await redis.client.get("any-user");
|
||||||
|
if (anyuser) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const anyuser = await knex("users").select("id").first();
|
||||||
|
|
||||||
|
if (env.REDIS_ENABLED && anyuser) {
|
||||||
|
redis.client.set("any-user", JSON.stringify(anyuser), "EX", 60 * 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
return !!anyuser;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
add,
|
add,
|
||||||
create,
|
create,
|
||||||
find,
|
find,
|
||||||
|
findAny,
|
||||||
getAdmin,
|
getAdmin,
|
||||||
remove,
|
remove,
|
||||||
totalAdmin,
|
totalAdmin,
|
||||||
|
@ -28,6 +28,14 @@ router.post(
|
|||||||
asyncHandler(auth.signup)
|
asyncHandler(auth.signup)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/create-admin",
|
||||||
|
locals.viewTemplate("partials/auth/form_admin"),
|
||||||
|
validators.createAdmin,
|
||||||
|
asyncHandler(helpers.verify),
|
||||||
|
asyncHandler(auth.createAdminUser)
|
||||||
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/change-password",
|
"/change-password",
|
||||||
locals.viewTemplate("partials/settings/change_password"),
|
locals.viewTemplate("partials/settings/change_password"),
|
||||||
|
@ -28,6 +28,11 @@ router.get(
|
|||||||
asyncHandler(renders.logout)
|
asyncHandler(renders.logout)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/create-admin",
|
||||||
|
asyncHandler(renders.createAdmin)
|
||||||
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/404",
|
"/404",
|
||||||
asyncHandler(auth.jwtLoosePage),
|
asyncHandler(auth.jwtLoosePage),
|
||||||
|
3
server/views/create_admin.hbs
Normal file
3
server/views/create_admin.hbs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{{> header}}
|
||||||
|
{{> auth/form_admin}}
|
||||||
|
{{> footer}}
|
@ -23,7 +23,10 @@
|
|||||||
{{#if errors.password}}<p class="error">{{errors.password}}</p>{{/if}}
|
{{#if errors.password}}<p class="error">{{errors.password}}</p>{{/if}}
|
||||||
</label>
|
</label>
|
||||||
<div class="buttons-wrapper">
|
<div class="buttons-wrapper">
|
||||||
<button type="submit" class="primary login">
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="primary login {{#if disallow_registration}}full{{else}}{{#unless mail_enabled}}full{{/unless}}{{/if}}"
|
||||||
|
>
|
||||||
<span>{{> icons/login}}</span>
|
<span>{{> icons/login}}</span>
|
||||||
<span>{{> icons/spinner}}</span>
|
<span>{{> icons/spinner}}</span>
|
||||||
Log in
|
Log in
|
||||||
|
40
server/views/partials/auth/form_admin.hbs
Normal file
40
server/views/partials/auth/form_admin.hbs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<form id="login-signup" hx-post="/api/auth/create-admin" hx-swap="outerHTML">
|
||||||
|
<h2 class="admin-form-title">
|
||||||
|
Create an Admin account first:
|
||||||
|
</h2>
|
||||||
|
<label class="{{#if errors.email}}error{{/if}}">
|
||||||
|
Email address:
|
||||||
|
<input
|
||||||
|
name="email"
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
autofocus="true"
|
||||||
|
placeholder="Email address..."
|
||||||
|
hx-preserve="true"
|
||||||
|
/>
|
||||||
|
{{#if errors.email}}<p class="error">{{errors.email}}</p>{{/if}}
|
||||||
|
</label>
|
||||||
|
<label class="{{#if errors.password}}error{{/if}}">
|
||||||
|
Password:
|
||||||
|
<input
|
||||||
|
name="password"
|
||||||
|
id="password"
|
||||||
|
type="password"
|
||||||
|
placeholder="Password..."
|
||||||
|
hx-preserve="true"
|
||||||
|
/>
|
||||||
|
{{#if errors.password}}<p class="error">{{errors.password}}</p>{{/if}}
|
||||||
|
</label>
|
||||||
|
<div class="buttons-wrapper admin-form">
|
||||||
|
<button type="submit" class="secondary full">
|
||||||
|
<span>{{> icons/new_user}}</span>
|
||||||
|
<span>{{> icons/spinner}}</span>
|
||||||
|
Create admin account
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{{#unless errors}}
|
||||||
|
{{#if error}}
|
||||||
|
<p class="error">{{error}}</p>
|
||||||
|
{{/if}}
|
||||||
|
{{/unless}}
|
||||||
|
</form>
|
@ -1021,6 +1021,8 @@ form#login-signup .buttons-wrapper button {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form#login-signup .buttons-wrapper button.full { flex-basis: 100%; }
|
||||||
|
|
||||||
form#login-signup a.forgot-password {
|
form#login-signup a.forgot-password {
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@ -1037,6 +1039,13 @@ form#login-signup p.error {
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-form-title {
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 300;
|
||||||
|
margin: 0 0 3rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.login-signup-message {
|
.login-signup-message {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
margin-top: 3rem;
|
margin-top: 3rem;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user