mirror of
https://github.com/Akif9748/akf-forum.git
synced 2024-12-22 07:39:08 +03:00
HTTP codes, approval system for user, email
This commit is contained in:
parent
32c2d3d1ca
commit
764dcc93f0
16 changed files with 101 additions and 26 deletions
12
README.md
12
README.md
|
@ -16,7 +16,13 @@ Edit `config.json` for default themes (`black` or `default`) of users, and forum
|
|||
`"discord_auth": "your_app_id"` in config.json.
|
||||
Add your app secret to `.env` as `DISCORD_SECRET`.
|
||||
Create a redirect url in discord developer portal:
|
||||
`https://forum_url.com/discord_auth/hash`
|
||||
`https://forum_url.com/auth/discord`
|
||||
|
||||
### EMAIL AUTH:
|
||||
You can configure it. Just edit `config.json` and `.env` files.
|
||||
`"email_auth": true` in config.json.
|
||||
Add your email credentials to `.env` as `EMAIL_USER` and `EMAIL_PASS`.
|
||||
Add your email domain to `.env` as `EMAIL_SERVICE`.
|
||||
|
||||
## API
|
||||
Akf-forum has got an API for AJAX (fetch), other clients etc. And, you can learn about API in `APIDOCS.md`.
|
||||
|
@ -45,14 +51,14 @@ Akf-forum has got an API for AJAX (fetch), other clients etc. And, you can learn
|
|||
- upload other photos, model for it
|
||||
- categories page is need a update, thread count in category
|
||||
- Disable last seen button for web.
|
||||
- email auth.
|
||||
- old contents / titles add to forum interface
|
||||
- add ban button to user profile.
|
||||
- change password.
|
||||
- add approval threads page.
|
||||
- who liked a message for web.
|
||||
- edit config from web admin panel.
|
||||
|
||||
- user.state for ban, delete, etc.
|
||||
- Add a feature list to README.md
|
||||
## Major Version History
|
||||
- V4: Caching
|
||||
- V3: New Theme
|
||||
|
|
|
@ -15,5 +15,6 @@
|
|||
},
|
||||
"discord_auth": "",
|
||||
"defaultThreadState": "OPEN",
|
||||
"email_auth": false,
|
||||
"host": "https://akf-forum.glitch.me"
|
||||
}
|
6
index.js
6
index.js
|
@ -22,19 +22,21 @@ app.ips = [];
|
|||
app.set("view engine", "ejs");
|
||||
app.set("limits", limits);
|
||||
|
||||
app.use(express.static("public"), express.json(), express.urlencoded({extended:true}), IP(),
|
||||
app.use(express.static("public"), express.json(), express.urlencoded({ extended: true }), IP(),
|
||||
SES({ secret: process.env.SECRET, store: MS.create({ clientPromise: DB, stringify: false }), resave: true, saveUninitialized: true }),
|
||||
async (req, res, next) => {
|
||||
if (app.ips.includes(req.clientIp)) return res.status(403).send("You are banned from this forum.");
|
||||
|
||||
req.user = req.session.userID ? await UserModel.findOneAndUpdate({ id: req.session.userID }, {
|
||||
lastSeen: Date.now(), $addToSet: { ips: req.clientIp }
|
||||
}): null;
|
||||
}) : null;
|
||||
res.reply = (page, options = {}, status = 200) => res.status(status)
|
||||
.render(page, { user: req.user, theme: req.user?.theme || def_theme, forum_name, description, ...options });
|
||||
|
||||
res.error = (type, error) => res.reply("error", { type, error }, type);
|
||||
|
||||
if (req.user && !req.user.approved && !req.url.startsWith("/auth/email")) return res.error(403, "Your account is not approved yet.");
|
||||
|
||||
if (req.user?.deleted) {
|
||||
req.session.destroy();
|
||||
return res.error(403, "Your account has been deleted.");
|
||||
|
|
19
lib.js
19
lib.js
|
@ -1,5 +1,7 @@
|
|||
const RL = require('express-rate-limit');
|
||||
|
||||
const nodemailer = require("nodemailer");
|
||||
const config = require("./config.json");
|
||||
require("dotenv").config();
|
||||
module.exports = {
|
||||
threadEnum: ["OPEN", "APPROVAL", "DELETED"],
|
||||
themes: ["default", "black"],
|
||||
|
@ -8,6 +10,17 @@ module.exports = {
|
|||
windowMs, max, standardHeaders: true, legacyHeaders: false,
|
||||
handler: (req, res, next, opts) => !req.user?.admin ? res.error(opts.statusCode, "You are begin ratelimited") : next()
|
||||
})
|
||||
}
|
||||
},
|
||||
emailRegEx: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (config.email_auth)
|
||||
module.exports.transporter = nodemailer.createTransport({
|
||||
service: process.env.EMAIL_SERVICE, direct: true, secure: true,
|
||||
auth: {
|
||||
user: process.env.EMAIL_USER,
|
||||
pass: process.env.EMAIL_PASS
|
||||
}
|
||||
});
|
||||
|
|
|
@ -12,13 +12,12 @@ const schema = new mongoose.Schema({
|
|||
|
||||
title: { type: String, maxlength: limits.title },
|
||||
oldTitles: [String],
|
||||
|
||||
|
||||
time: { type: Date, default: Date.now },
|
||||
edited: { type: Boolean, default: false },
|
||||
state: { type: String, default: defaultThreadState, enum: threadEnum },
|
||||
messages: [String],
|
||||
views: { type: Number, default: 0 }
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const mongoose = require("mongoose")
|
||||
const { def_theme, limits } = require("../config.json");
|
||||
const { def_theme, limits, email_auth } = require("../config.json");
|
||||
const schema = new mongoose.Schema({
|
||||
id: { type: String, unique: true },
|
||||
discordID: { type: String },
|
||||
|
@ -15,7 +15,10 @@ const schema = new mongoose.Schema({
|
|||
hideLastSeen: { type: Boolean, default: false },
|
||||
ips: { type: [String], default: [], select: false },
|
||||
password: { type: String, select: false },
|
||||
discord_code: { type: String, select: false }
|
||||
discord_code: { type: String, select: false },
|
||||
approved: { type: Boolean, default: !email_auth },
|
||||
email: { type: String, select: false },
|
||||
email_code: { type: String, select: false },
|
||||
});
|
||||
|
||||
schema.methods.takeId = async function () {
|
||||
|
|
14
package-lock.json
generated
14
package-lock.json
generated
|
@ -19,6 +19,7 @@
|
|||
"mongoose": "^6.6.1",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"node-fetch": "^2.6.7",
|
||||
"nodemailer": "^6.7.8",
|
||||
"request-ip": "^3.3.0"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -1308,6 +1309,14 @@
|
|||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nodemailer": {
|
||||
"version": "6.7.8",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.8.tgz",
|
||||
"integrity": "sha512-2zaTFGqZixVmTxpJRCFC+Vk5eGRd/fYtvIR+dl5u9QXLTQWGIf48x/JXvo58g9sa0bU6To04XUv554Paykum3g==",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nopt": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
|
||||
|
@ -2822,6 +2831,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"nodemailer": {
|
||||
"version": "6.7.8",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.8.tgz",
|
||||
"integrity": "sha512-2zaTFGqZixVmTxpJRCFC+Vk5eGRd/fYtvIR+dl5u9QXLTQWGIf48x/JXvo58g9sa0bU6To04XUv554Paykum3g=="
|
||||
},
|
||||
"nopt": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
"mongoose": "^6.6.1",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"node-fetch": "^2.6.7",
|
||||
"nodemailer": "^6.7.8",
|
||||
"request-ip": "^3.3.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ app.use(async (req, res, next) => {
|
|||
const user = await UserModel.findOne({ name });
|
||||
|
||||
if (!user || user.deleted) return res.error(401, `We don't have any user with name ${name}.`)
|
||||
if (!user.approved) return res.error(401, "Your account is not approved yet.");
|
||||
|
||||
if (!await bcrypt.compare(password, user.password)) return res.error(401, 'Incorrect Password!');
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ app.delete("/:id/", async (req, res) => {
|
|||
|
||||
if (user.id != message.authorID && !user.admin)
|
||||
return res.error(403, "You have not got permission for this.");
|
||||
if (message.deleted) return res.error(403, "This message is already deleted.");
|
||||
if (message.deleted) return res.error(404, "This message is already deleted.");
|
||||
|
||||
message.deleted = true;
|
||||
await message.save()
|
||||
|
|
|
@ -99,7 +99,7 @@ app.delete("/:id/", async (req, res) => {
|
|||
if (user.id != thread.authorID && !user.admin)
|
||||
return res.error(403, "You have not got permission for this.");
|
||||
|
||||
if (thread.state == "DELETED") return res.error(403, "This thread is already deleted.");
|
||||
if (thread.state == "DELETED") return res.error(404, "This thread is already deleted.");
|
||||
thread.state = "DELETED";
|
||||
await thread.save();
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ const { Router } = require("express")
|
|||
const { UserModel } = require("../models");
|
||||
const fetch = require("node-fetch");
|
||||
const app = Router();
|
||||
const { host, discord_auth } = require("../config.json")
|
||||
const { host, discord_auth, email_auth } = require("../config.json")
|
||||
|
||||
app.get("/discord", async (req, res) => {
|
||||
const client_id = discord_auth;
|
||||
|
@ -22,7 +22,7 @@ app.get("/discord", async (req, res) => {
|
|||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
if (!response.ok) return res.error(500, "Bad request to discord");
|
||||
|
||||
|
@ -39,7 +39,7 @@ app.get("/discord", async (req, res) => {
|
|||
return res.error(403, "Your forum account is already linked to a discord account.");
|
||||
|
||||
if (forum)
|
||||
return res.error(403, "This discord account is already linked to a forum account.");
|
||||
return res.error(409, "This discord account is already linked to a forum account.");
|
||||
|
||||
req.user.discordID = discord.id;
|
||||
req.user.discord_code = code;
|
||||
|
@ -75,7 +75,7 @@ app.get("/discord", async (req, res) => {
|
|||
});
|
||||
|
||||
app.delete("/discord", async (req, res) => {
|
||||
if (!req.user) return res.error(403, "You are not logged in");
|
||||
if (!req.user) return res.error(401, "You are not logged in");
|
||||
if (!req.user.discordID) return res.error(403, "You don't have a discord account linked to your forum account.");
|
||||
req.user.discordID = undefined;
|
||||
req.user.discord_code = undefined;
|
||||
|
@ -83,5 +83,16 @@ app.delete("/discord", async (req, res) => {
|
|||
res.send("Your discord account has been unlinked from your forum account.");
|
||||
});
|
||||
|
||||
app.get("/email", async (req, res) => {
|
||||
if (!email_auth) return res.error(404, "Email auth is disabled");
|
||||
if (!req.user) return res.error(401, "You are not logged in");
|
||||
if (req.user.email) return res.error(403, "You already have an email linked to your account.");
|
||||
const { code } = req.query;
|
||||
if (!code) return res.error(400, "No code provided");
|
||||
if (code !== req.user.email_code) return res.error(403, "Invalid code");
|
||||
req.user.approved = true;
|
||||
await req.user.save();
|
||||
res.send("Your email has been linked to your forum account.");
|
||||
});
|
||||
|
||||
module.exports = app;
|
|
@ -13,8 +13,8 @@ app.post("/", async (req, res) => {
|
|||
if (!name || !password) return res.error(400, "You forgot entering some values")
|
||||
|
||||
const member = await UserModel.findOne({ name }, "+password");
|
||||
if (!member || member.deleted) return res.error(403, 'Incorrect username!');
|
||||
if (!await bcrypt.compare(password, member.password)) return res.error(403, 'Incorrect password!');
|
||||
if (!member || member.deleted) return res.error(401, 'Incorrect username!');
|
||||
if (!await bcrypt.compare(password, member.password)) return res.error(401, 'Incorrect password!');
|
||||
|
||||
req.session.userID = member.id;
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
const { UserModel } = require("../models");
|
||||
const { Router } = require("express")
|
||||
const bcrypt = require("bcrypt");
|
||||
const { RL } = require('../lib');
|
||||
const { RL, transporter, emailRegEx } = require('../lib');
|
||||
const app = Router();
|
||||
|
||||
app.get("/", (req, res) => res.reply("register", { user: null, discord: req.app.get("discord_auth") }));
|
||||
const { email_auth, forum_name, host } = require("../config.json");
|
||||
app.get("/", (req, res) => res.reply("register", { user: null, discord: req.app.get("discord_auth"), mail: email_auth }));
|
||||
|
||||
app.post("/", RL(24 * 60 * 60_000, 5), async (req, res) => {
|
||||
|
||||
|
@ -26,9 +26,30 @@ app.post("/", RL(24 * 60 * 60_000, 5), async (req, res) => {
|
|||
}
|
||||
|
||||
await user.takeId()
|
||||
if (user.id === "0") user.admin = true;
|
||||
if (user.id === "0")
|
||||
user.admin = true;
|
||||
else if (email_auth) {
|
||||
const email = req.body.email;
|
||||
if (!email || !emailRegEx.test(email)) return res.error(400, "E-mail is not valid");
|
||||
if (await UserModel.exists({ email })) return res.error(400, "E-mail is already in use");
|
||||
user.email = email;
|
||||
user.email_code = await bcrypt.hash(`${Date.now()}-${Math.floor(Math.random() * 1e20)}`, 10)
|
||||
|
||||
user.password = await bcrypt.hash(password, await bcrypt.genSalt(10));
|
||||
transporter.sendMail({
|
||||
from: transporter.options.auth.user,
|
||||
to: email,
|
||||
subject: name + ", please verify your email",
|
||||
html: `
|
||||
<h1>Verify your email in ${forum_name}-forum</h1>
|
||||
<a href="${host}/auth/email?code=${user.email_code}">Click here to verify your email</a>
|
||||
`
|
||||
}, (err, info) => {
|
||||
if (err) return res.error(500, "Failed to send email");
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
user.password = await bcrypt.hash(password, 10);
|
||||
await user.save();
|
||||
|
||||
req.session.userID = user.id;
|
||||
|
|
|
@ -6,7 +6,7 @@ mongoose.connect(process.env.MONGO_DB_URL, () => console.log("Database is connec
|
|||
const { UserModel } = require("../models");
|
||||
(async () => {
|
||||
|
||||
const member= await UserModel.get("0");
|
||||
const member = await UserModel.get("0");
|
||||
member.admin = true;
|
||||
console.log(await member.save());
|
||||
})();
|
|
@ -15,6 +15,9 @@
|
|||
|
||||
|
||||
<form action="/register" method="post">
|
||||
<% if (mail) { %>
|
||||
<input type="email" name="email" placeholder="Email" class="input" required>
|
||||
<% } %>
|
||||
<input type="text" name="name" placeholder="Username" class="input" required>
|
||||
<input type="password" name="password" placeholder="Password" class="input" required>
|
||||
<textarea class="input" name="about" rows="4" placeholder="About you... You can use markdown"></textarea>
|
||||
|
|
Loading…
Reference in a new issue