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 nanoid = require("nanoid");
|
||||
|
||||
const { ROLES } = require("../consts");
|
||||
const query = require("../queries");
|
||||
const utils = require("../utils");
|
||||
const redis = require("../redis");
|
||||
@ -26,13 +27,12 @@ function authenticate(type, error, isStrict, redirect) {
|
||||
(user && isStrict && !user.verified) ||
|
||||
(user && user.banned))
|
||||
) {
|
||||
const path = user.banned ? "/logout" : "/login";
|
||||
if (redirect === "page") {
|
||||
res.redirect(path);
|
||||
res.redirect("/logout");
|
||||
return;
|
||||
}
|
||||
if (redirect === "header") {
|
||||
res.setHeader("HX-Redirect", path);
|
||||
res.setHeader("HX-Redirect", "/logout");
|
||||
res.send("NOT_AUTHENTICATED");
|
||||
return;
|
||||
}
|
||||
@ -125,6 +125,33 @@ async function signup(req, res) {
|
||||
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) {
|
||||
const token = utils.signToken(req.user);
|
||||
|
||||
@ -382,6 +409,7 @@ module.exports = {
|
||||
changeEmailRequest,
|
||||
changePassword,
|
||||
cooldown,
|
||||
createAdminUser,
|
||||
featureAccess,
|
||||
featureAccessPage,
|
||||
generateApiKey,
|
||||
|
@ -3,16 +3,29 @@ const utils = require("../utils");
|
||||
const env = require("../env");
|
||||
|
||||
async function homepage(req, res) {
|
||||
const isThereAUser = await query.user.findAny();
|
||||
if (!isThereAUser) {
|
||||
res.redirect("/create-admin");
|
||||
return;
|
||||
}
|
||||
|
||||
res.render("homepage", {
|
||||
title: "Modern open source URL shortener",
|
||||
});
|
||||
}
|
||||
|
||||
function login(req, res) {
|
||||
async function login(req, res) {
|
||||
if (req.user) {
|
||||
res.redirect("/");
|
||||
return;
|
||||
}
|
||||
|
||||
const isThereAUser = await query.user.findAny();
|
||||
if (!isThereAUser) {
|
||||
res.redirect("/create-admin");
|
||||
return;
|
||||
}
|
||||
|
||||
res.render("login", {
|
||||
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) {
|
||||
res.render("404", {
|
||||
title: "404 - Not found"
|
||||
@ -266,6 +290,7 @@ module.exports = {
|
||||
confirmLinkDelete,
|
||||
confirmUserBan,
|
||||
confirmUserDelete,
|
||||
createAdmin,
|
||||
createUser,
|
||||
getReportEmail,
|
||||
getSupportEmail,
|
||||
|
@ -417,6 +417,19 @@ const login = [
|
||||
.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 = [
|
||||
body("currentpassword", "Password is not valid.")
|
||||
.exists({ checkFalsy: true, checkNull: true })
|
||||
@ -593,6 +606,7 @@ module.exports = {
|
||||
changePassword,
|
||||
checkUser,
|
||||
cooldown,
|
||||
createAdmin,
|
||||
createLink,
|
||||
createUser,
|
||||
deleteLink,
|
||||
|
@ -38,6 +38,8 @@ async function add(params, user) {
|
||||
const data = {
|
||||
email: params.email,
|
||||
password: params.password,
|
||||
...(params.role && { role: params.role }),
|
||||
...(params.verified !== undefined && { verified: params.verified }),
|
||||
verification_token: uuid(),
|
||||
verification_expires: utils.dateToUTC(addMinutes(new Date(), 60))
|
||||
};
|
||||
@ -216,10 +218,27 @@ async function create(params) {
|
||||
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 = {
|
||||
add,
|
||||
create,
|
||||
find,
|
||||
findAny,
|
||||
getAdmin,
|
||||
remove,
|
||||
totalAdmin,
|
||||
|
@ -28,6 +28,14 @@ router.post(
|
||||
asyncHandler(auth.signup)
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/create-admin",
|
||||
locals.viewTemplate("partials/auth/form_admin"),
|
||||
validators.createAdmin,
|
||||
asyncHandler(helpers.verify),
|
||||
asyncHandler(auth.createAdminUser)
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/change-password",
|
||||
locals.viewTemplate("partials/settings/change_password"),
|
||||
|
@ -28,6 +28,11 @@ router.get(
|
||||
asyncHandler(renders.logout)
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/create-admin",
|
||||
asyncHandler(renders.createAdmin)
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/404",
|
||||
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}}
|
||||
</label>
|
||||
<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/spinner}}</span>
|
||||
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;
|
||||
}
|
||||
|
||||
form#login-signup .buttons-wrapper button.full { flex-basis: 100%; }
|
||||
|
||||
form#login-signup a.forgot-password {
|
||||
align-self: flex-start;
|
||||
font-size: 14px;
|
||||
@ -1037,6 +1039,13 @@ form#login-signup p.error {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.admin-form-title {
|
||||
font-size: 26px;
|
||||
font-weight: 300;
|
||||
margin: 0 0 3rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-signup-message {
|
||||
flex: 1 1 auto;
|
||||
margin-top: 3rem;
|
||||
|
Loading…
x
Reference in New Issue
Block a user