diff --git a/backend/.env.example b/backend/.env.example index fc36907..723d7bf 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,15 +1,3 @@ -HOSTNAME=localhost -CORS_HOSTNAME=http://localhost:8080 -RELEASE=stable -NOTIFICATION= -NOTIFICATION_TYPE=info -SITE_NAME=Colubrina -ALLOW_REGISTRATIONS=true -PUBLIC_USERS=false -EMAIL_VERIFICATION=false -EMAIL_SMTP_HOST=smtp.myhost.com -EMAIL_SMTP_PORT=587 -EMAIL_SMTP_USER=colubrina@myhost.com -EMAIL_SMTP_FROM=colubrina@myhost.com -EMAIL_SMTP_PASSWORD=myPassword -EMAIL_SMTP_SECURE=false \ No newline at end of file +.ENV is no longer in use, replaced with: + - config/database.json - Database credentials + - config/config.json - Colubrina configuration (used to be .env) \ No newline at end of file diff --git a/backend/.gitignore b/backend/.gitignore index b601da8..98a1932 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -61,6 +61,7 @@ yarn-error.log* # Config folder config/config.json +config/database.json # Editor directories and files .idea *.suo diff --git a/backend/.sequelizerc b/backend/.sequelizerc new file mode 100644 index 0000000..a09f306 --- /dev/null +++ b/backend/.sequelizerc @@ -0,0 +1,5 @@ +var path = require('path') + +module.exports = { + 'config': path.resolve('config/database.json'), +} \ No newline at end of file diff --git a/backend/config/config.example.json b/backend/config/config.example.json index 1fbed0c..2851564 100644 --- a/backend/config/config.example.json +++ b/backend/config/config.example.json @@ -1,23 +1,18 @@ { - "development": { - "username": "username", - "password": "password", - "database": "database", - "host": "host", - "dialect": "mariadb" - }, - "test": { - "username": "username", - "password": "password", - "database": "database", - "host": "host", - "dialect": "mariadb" - }, - "production": { - "username": "username", - "password": "password", - "database": "database", - "host": "host", - "dialect": "mariadb" - } + "hostname": "localhost", + "corsHostname": "http://localhost:8080", + "siteName": "Colubrina", + "allowRegistrations": true, + "notification": "", + "notificationType": "", + "release": "stable", + "publicUsers": true, + "emailVerification": false, + "emailSMTPHost": "mail.example.com", + "emailSMTPPort": 587, + "emailSMTPUser": "colubrina@example.com", + "emailSMTPFrom": "colubrina@example.com", + "emailSMTPPassword": "", + "emailSMTPSecure": false, + "rules": "Write your instance rules here." } diff --git a/backend/config/database.example.json b/backend/config/database.example.json new file mode 100644 index 0000000..1fbed0c --- /dev/null +++ b/backend/config/database.example.json @@ -0,0 +1,23 @@ +{ + "development": { + "username": "username", + "password": "password", + "database": "database", + "host": "host", + "dialect": "mariadb" + }, + "test": { + "username": "username", + "password": "password", + "database": "database", + "host": "host", + "dialect": "mariadb" + }, + "production": { + "username": "username", + "password": "password", + "database": "database", + "host": "host", + "dialect": "mariadb" + } +} diff --git a/backend/index.js b/backend/index.js index a735b53..f065c81 100644 --- a/backend/index.js +++ b/backend/index.js @@ -5,6 +5,7 @@ let app = express() let bodyParser = require("body-parser") let os = require("os") app.set("trust proxy", true) +app.locals.config = require("./config/config.json") const socket = require("./lib/socket") const server = require("http").createServer(app) @@ -22,15 +23,17 @@ app.use("/api/v1/mediaproxy", require("./routes/mediaproxy.js")) app.use("/api/v1/associations", require("./routes/associations.js")) app.get("/api/v1/state", async (req, res) => { res.json({ - release: process.env.RELEASE, - loading: true, - notification: process.env.NOTIFICATION, - notificationType: process.env.NOTIFICATION_TYPE, + release: req.app.locals.config.release, + notification: req.app.locals.config.notification, + notificationType: req.app.locals.config.notificationType, latestVersion: require("../frontend/package.json").version, - name: process.env.SITE_NAME, - allowRegistrations: process.env.ALLOW_REGISTRATIONS === "true", - publicUsers: process.env.PUBLIC_USERS === "true", - emailVerification: process.env.EMAIL_VERIFICATION === "true" + allowRegistrations: req.app.locals.config.allowRegistrations, + publicUsers: req.app.locals.config.publicUsers, + emailVerification: req.app.locals.config.emailVerification, + rules: req.app.locals.config.rules, + name: req.app.locals.config.siteName, + loading: true, + isColubrinaServer: true }) }) diff --git a/backend/lib/socket.js b/backend/lib/socket.js index 8471311..ed68647 100644 --- a/backend/lib/socket.js +++ b/backend/lib/socket.js @@ -4,7 +4,7 @@ module.exports = { init(app, server) { const io = require("socket.io")(server, { cors: { - origin: [process.env.CORS_HOSTNAME], + origin: [app.locals.config.corsHostname], methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"] } }) @@ -19,10 +19,15 @@ module.exports = { console.log(socket.user.id) socket.join(user.id) socket.emit("siteState", { - release: process.env.RELEASE, - notification: process.env.NOTIFICATION, - notificationType: process.env.NOTIFICATION_TYPE, - latestVersion: require("../../frontend/package.json").version + release: app.locals.config.release, + notification: app.locals.config.notification, + notificationType: app.locals.config.notificationType, + latestVersion: require("../../frontend/package.json").version, + allowRegistrations: app.locals.config.allowRegistrations, + publicUsers: app.locals.config.publicUsers, + emailVerification: app.locals.config.emailVerification, + rules: app.locals.config.rules, + name: app.locals.config.siteName }) const friends = await Friend.findAll({ where: { @@ -114,10 +119,15 @@ module.exports = { } else { socket.join(-1) socket.emit("siteState", { - release: process.env.RELEASE, - notification: process.env.NOTIFICATION, - notificationType: process.env.NOTIFICATION_TYPE, - latestVersion: require("../../frontend/package.json").version + release: app.locals.config.release, + notification: app.locals.config.notification, + notificationType: app.locals.config.notificationType, + latestVersion: require("../../frontend/package.json").version, + allowRegistrations: app.locals.config.allowRegistrations, + publicUsers: app.locals.config.publicUsers, + emailVerification: app.locals.config.emailVerification, + rules: app.locals.config.rules, + name: app.locals.config.siteName }) socket.emit("unauthorized", { message: "Please reauth." diff --git a/backend/models/index.js b/backend/models/index.js index 439bb75..9604585 100644 --- a/backend/models/index.js +++ b/backend/models/index.js @@ -5,7 +5,7 @@ const path = require("path") const Sequelize = require("sequelize") const basename = path.basename(__filename) const env = process.env.NODE_ENV || "development" -const config = require(__dirname + "/../config/config.json")[env] +const config = require(__dirname + "/../config/database.json")[env] const db = {} let sequelize diff --git a/backend/package.json b/backend/package.json index ab143d2..a2b690c 100644 --- a/backend/package.json +++ b/backend/package.json @@ -5,7 +5,7 @@ "license": "GPL-3.0", "scripts": { "start": "node .", - "serve": "nodemon" + "serve": "nodemon . --ignore config" }, "dependencies": { "argon2": "^0.28.7", diff --git a/backend/routes/admin.js b/backend/routes/admin.js index f38e3ef..361bb6b 100644 --- a/backend/routes/admin.js +++ b/backend/routes/admin.js @@ -7,6 +7,7 @@ const { Op } = require("sequelize") const dayjs = require("dayjs") const fs = require("fs") const os = require("os") +const argon2 = require("argon2") router.all("*", auth, async (req, res, next) => { try { @@ -22,7 +23,7 @@ router.all("*", auth, async (req, res, next) => { router.all("*", auth, async (req, res, next) => { try { - if (!req.user.emailVerified && process.env.EMAIL_VERIFICATION === "true") { + if (!req.user.emailVerified && req.app.locals.config.emailVerification) { throw Errors.emailVerificationRequired } else { next() @@ -53,8 +54,45 @@ router.get("/", auth, async (req, res, next) => { } }) }) - } catch (err) { - return next(err) + } catch (e) { + next(e) + } +}) + +router.put("/user/:id", auth, async (req, res, next) => { + try { + const user = await User.findOne({ + where: { + id: req.params.id + } + }) + if (!user) { + throw Errors.communicationsUserNotFound + } else { + await user.update({ + banned: req.body.banned + }) + res.json(user) + } + } catch (e) { + next(e) + } +}) + +router.post("/user", auth, async (req, res, next) => { + try { + const user = await User.create({ + username: req.body.username, + password: await argon2.hash(req.body.password), + email: req.body.email, + emailVerified: req.body.emailVerified, + admin: false, + banned: false, + lastSeenAt: new Date() + }) + res.json(user) + } catch (e) { + next(e) } }) @@ -231,19 +269,23 @@ router.put("/state", auth, async (req, res, next) => { } try { const io = req.app.get("io") - setEnvValue("ALLOW_REGISTRATIONS", req.body.allowRegistrations) + req.app.locals.config.allowRegistrations = req.body.allowRegistrations + req.app.locals.config.rules = req.body.rules if (req.body.broadcastType === "permanent") { - setEnvValue("NOTIFICATION", req.body.notification) - setEnvValue("NOTIFICATION_TYPE", req.body.notificationType) - process.env.NOTIFICATION = req.body.notification - process.env.NOTIFICATION_TYPE = req.body.notificationType + req.app.locals.config.notification = req.body.notification + req.app.locals.config.notificationType = req.body.notificationType } io.emit("siteState", { notification: req.body.notification, notificationType: req.body.notificationType, latestVersion: require("../../frontend/package.json").version, - allowRegistrations: req.body.allowRegistrations + allowRegistrations: req.body.allowRegistrations, + rules: req.body.rules }) + fs.writeFileSync( + "./config/config.json", + JSON.stringify(req.app.locals.config, null, 2) + ) res.sendStatus(204) } catch (err) { return next(err) diff --git a/backend/routes/associations.js b/backend/routes/associations.js index 3202a3d..4bbba5c 100644 --- a/backend/routes/associations.js +++ b/backend/routes/associations.js @@ -13,7 +13,7 @@ const { router.all("*", auth, async (req, res, next) => { try { - if (!req.user.emailVerified && process.env.EMAIL_VERIFICATION === "true") { + if (!req.user.emailVerified && req.app.locals.config.emailVerification) { throw Errors.emailVerificationRequired } else { next() diff --git a/backend/routes/communications.js b/backend/routes/communications.js index f74361b..0a71928 100644 --- a/backend/routes/communications.js +++ b/backend/routes/communications.js @@ -155,7 +155,7 @@ async function createMessage(req, type, content, association, userId) { router.all("*", auth, async (req, res, next) => { try { - if (!req.user.emailVerified && process.env.EMAIL_VERIFICATION === "true") { + if (!req.user.emailVerified && req.app.locals.config.emailVerification) { throw Errors.emailVerificationRequired } else { next() @@ -376,7 +376,7 @@ router.get("/mutual/:id/groups", auth, async (req, res, next) => { router.get("/users", auth, async (req, res, next) => { try { - if (process.env.PUBLIC_USERS === "true") { + if (req.app.locals.config.publicUsers) { const users = await User.findAll({ attributes: [ "id", diff --git a/backend/routes/friends.js b/backend/routes/friends.js index 2d95acb..18eec1e 100644 --- a/backend/routes/friends.js +++ b/backend/routes/friends.js @@ -6,7 +6,7 @@ const router = express.Router() router.all("*", auth, async (req, res, next) => { try { - if (!req.user.emailVerified && process.env.EMAIL_VERIFICATION === "true") { + if (!req.user.emailVerified && req.app.locals.config.emailVerification) { throw Errors.emailVerificationRequired } else { next() diff --git a/backend/routes/user.js b/backend/routes/user.js index f944e17..789b6ab 100644 --- a/backend/routes/user.js +++ b/backend/routes/user.js @@ -72,7 +72,7 @@ const upload = multer({ router.post("/verify/resend", auth, mailLimiter, async (req, res, next) => { try { - if (process.env.EMAIL_VERIFICATION !== "true") { + if (!req.app.locals.config.emailVerification) { throw Errors.invalidParameter("Email verification is disabled") } if (req.user.emailVerified) { @@ -85,20 +85,20 @@ router.post("/verify/resend", auth, mailLimiter, async (req, res, next) => { const mailGenerator = new Mailgen({ theme: "default", product: { - name: process.env.SITE_NAME, - link: process.env.CORS_HOSTNAME + name: req.app.locals.config.siteName, + link: req.app.locals.config.corsHostname } }) const email = { body: { name: req.user.username, - intro: `${process.env.SITE_NAME} Account Verification`, + intro: `${req.app.locals.config.siteName} Account Verification`, action: { - instructions: `You are receiving this email because you registered on ${process.env.SITE_NAME}, please use the link below to verify your account.`, + instructions: `You are receiving this email because you registered on ${req.app.locals.config.siteName}, please use the link below to verify your account.`, button: { color: "#1A97FF", text: "Account Verification", - link: process.env.CORS_HOSTNAME + "/email/confirm/" + token + link: req.app.locals.config.corsHostname + "/email/confirm/" + token } }, outro: "If you did not register, please disregard this email." @@ -108,18 +108,18 @@ router.post("/verify/resend", auth, mailLimiter, async (req, res, next) => { const emailText = mailGenerator.generatePlaintext(email) const transporter = nodemailer.createTransport({ - host: process.env.EMAIL_SMTP_HOST, - port: process.env.EMAIL_SMTP_PORT, - secure: JSON.parse(process.env.EMAIL_SMTP_SECURE.toLowerCase()), + host: req.app.locals.config.emailSMTPHost, + port: req.app.locals.config.emailSMTPPort, + secure: req.app.locals.config.emailSMTPSecure, auth: { - user: process.env.EMAIL_SMTP_USER, - pass: process.env.EMAIL_SMTP_PASSWORD + user: req.app.locals.config.emailSMTPUser, + pass: req.app.locals.config.emailSMTPPassword } }) let info = await transporter.sendMail({ - from: process.env.EMAIL_SMTP_FROM, + from: req.app.locals.config.emailSMTPFrom, to: req.user.email, - subject: "Email Verification - " + process.env.SITE_NAME, + subject: "Email Verification - " + req.app.locals.config.siteName, text: emailText, html: emailBody }) @@ -135,7 +135,7 @@ router.post("/verify/resend", auth, mailLimiter, async (req, res, next) => { router.post("/verify/confirm/:token", async (req, res, next) => { try { - if (process.env.EMAIL_VERIFICATION !== "true") { + if (!req.app.locals.config.emailVerification) { throw Errors.invalidParameter("Email verification is disabled") } if (!req.params.token) { @@ -294,7 +294,7 @@ router.post("/register", limiter, async (req, res, next) => { } } try { - if (process.env.ALLOW_REGISTRATIONS !== "true") { + if (!req.app.locals.config.allowRegistrations) { throw Errors.registrationsDisabled } if (req.body.password.length < 8) { diff --git a/cli/index.js b/cli/index.js index 1019575..3203082 100644 --- a/cli/index.js +++ b/cli/index.js @@ -9,6 +9,11 @@ const os = require("os") const { execSync } = require("child_process") console.log("Troplo/Colubrina CLI") +if (fs.existsSync("../backend/config/config.json")) { + console.log( + "Want to modify either the Colubrina, or database config? Check out the config files in backend/config." + ) +} console.log("Colubrina version", require("../frontend/package.json").version) async function checkForUpdates() { if (!process.argv.includes("--skip-update")) { @@ -139,10 +144,10 @@ async function testDB() { async function dbSetup() { await doSetupDB() fs.writeFileSync( - path.join(__dirname, "../backend/config/config.json"), - JSON.stringify(state.dbConfig) + path.join(__dirname, "../backend/config/database.json"), + JSON.stringify(state.dbConfig, null, 2) ) - console.log("config/config.json overwritten") + console.log("config/database.json overwritten") } async function runMigrations() { console.log("Running migrations") @@ -171,116 +176,61 @@ async function createUser() { console.log("User created") } async function configureDotEnv() { - function setEnvValue(key, value) { - const ENV_VARS = fs.readFileSync("../backend/.env", "utf8").split(os.EOL) - - // find the env we want based on the key - const target = ENV_VARS.indexOf( - ENV_VARS.find((line) => { - // (? { console.log("yarn install complete (frontend)") }) - if (fs.existsSync(path.join(__dirname, "../backend/.env"))) { - const option = await input.confirm(".env already exists, overwrite?", { - default: false - }) + if ( + fs.existsSync(path.join(__dirname, "../backend/config/config.json")) + ) { + const option = await input.confirm( + "Colubrina config already exists, overwrite?", + { + default: false + } + ) if (option) { await configureDotEnv() } @@ -314,10 +269,10 @@ async function init() { await configureDotEnv() } if ( - fs.existsSync(path.join(__dirname, "../backend/config/config.json")) + fs.existsSync(path.join(__dirname, "../backend/config/database.json")) ) { const option = await input.select( - `config/config.json already exists. Do you want to overwrite it?`, + `config/database.json already exists. Do you want to overwrite it?`, ["Yes", "No"] ) if (option === "Yes") { @@ -368,9 +323,9 @@ async function init() { console.log( "The Colubrina frontend can be built with `yarn build` in the root project directory, and is recommended to be served via NGINX, with a proxy_pass to the backend on /api and /socket.io." ) - } else if (option === "Update/create config file") { + } else if (option === "Update/create database config file") { await dbSetup() - console.log("config/config.json overwritten or created") + console.log("config/database.json overwritten or created") } else if (option === "Create user") { await createUser() } else if (option === "Run migrations") { diff --git a/frontend/package.json b/frontend/package.json index 76453af..77ea01a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "colubrina", - "version": "1.0.18", + "version": "1.0.19", "private": true, "author": "Troplo ", "scripts": { diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 5634ee8..d0a1958 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -624,6 +624,9 @@ export default { this.$store.state.site.latestVersion = state.latestVersion this.$store.state.site.notification = state.notification this.$store.state.site.notificationType = state.notificationType + this.$store.state.site.allowRegistrations = state.allowRegistrations + this.$store.state.site.rules = state.rules + this.$store.state.site.emailVerification = state.emailVerification }) // eslint-disable-next-line no-undef this.$store.dispatch("updateQuickSwitch") diff --git a/frontend/src/views/Admin/AdminState.vue b/frontend/src/views/Admin/AdminState.vue index 53c539d..c8f667e 100644 --- a/frontend/src/views/Admin/AdminState.vue +++ b/frontend/src/views/Admin/AdminState.vue @@ -32,6 +32,14 @@ label="Allow registrations" v-model="allowRegistrations" > + + + + mdi-language-markdownRules Preview: + + + {{ rules }} + Save @@ -47,6 +55,7 @@ export default { notification: "", notificationType: "info", allowRegistrations: true, + rules: "", notificationTypes: [ { text: "Info", value: "info" }, { text: "Success", value: "success" }, @@ -73,7 +82,8 @@ export default { notification: this.notification, notificationType: this.notificationType, broadcastType: this.broadcastType, - allowRegistrations: this.allowRegistrations + allowRegistrations: this.allowRegistrations, + rules: this.rules }) .then(() => { this.$toast.success("State updated") @@ -87,6 +97,7 @@ export default { this.notification = this.$store.state.site.notification this.notificationType = this.$store.state.site.notificationType this.allowRegistrations = this.$store.state.site.allowRegistrations + this.rules = this.$store.state.site.rules } } diff --git a/frontend/src/views/Admin/AdminUsers.vue b/frontend/src/views/Admin/AdminUsers.vue index f7cbecb..6b751b7 100644 --- a/frontend/src/views/Admin/AdminUsers.vue +++ b/frontend/src/views/Admin/AdminUsers.vue @@ -1,8 +1,38 @@ @@ -31,6 +76,13 @@ export default { data() { return { users: [], + create: { + dialog: false, + username: "", + email: "", + password: "", + emailVerified: false + }, headers: [ { text: "Index", @@ -68,11 +120,59 @@ export default { { text: "Last Seen At", value: "lastSeenAt" + }, + { + text: "Admin", + value: "admin" + }, + { + text: "Banned", + value: "banned" + }, + { + text: "Email Verified", + value: "emailVerified" + }, + { + text: "Actions", + value: "actions", + sortable: false } ] } }, methods: { + createUser() { + this.axios + .post(`/api/v1/admin/user`, this.create) + .then(() => { + this.getUsers() + this.$toast.success("Action performed successfully.") + this.create = { + dialog: false, + username: "", + email: "", + password: "", + emailVerified: false + } + }) + .catch((e) => { + AjaxErrorHandler(this.$store)(e) + }) + }, + banUser(item) { + this.axios + .put(`/api/v1/admin/user/${item.id}`, { + banned: !item.banned + }) + .then(() => { + this.getUsers() + this.$toast.success("Action performed successfully.") + }) + .catch((e) => { + AjaxErrorHandler(this.$store)(e) + }) + }, getUsers() { this.axios .get("/api/v1/admin/users") diff --git a/frontend/src/views/Login.vue b/frontend/src/views/Login.vue index 9d8d928..b3ac651 100644 --- a/frontend/src/views/Login.vue +++ b/frontend/src/views/Login.vue @@ -30,8 +30,7 @@

Login to - {{ $store.state.site.name }}beta + {{ $store.state.site.name }}

-
+
+ + + + + {{ $store.state.site.name }} Rules + + + + {{ + $store.state.site.rules + }} + + +
@@ -8,8 +22,7 @@

Register to - {{ $store.state.site.name }}beta + {{ $store.state.site.name }}

This instance has email verification enforced, ensure your email is correct. + + + + You need to view the rules first. + + + I have read and agree to the  + + instance rules . +