This commit is contained in:
Troplo 2022-08-07 00:35:00 +10:00
parent b292277ec4
commit 0d6e209fc6
22 changed files with 424 additions and 207 deletions

View File

@ -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
.ENV is no longer in use, replaced with:
- config/database.json - Database credentials
- config/config.json - Colubrina configuration (used to be .env)

1
backend/.gitignore vendored
View File

@ -61,6 +61,7 @@ yarn-error.log*
# Config folder
config/config.json
config/database.json
# Editor directories and files
.idea
*.suo

5
backend/.sequelizerc Normal file
View File

@ -0,0 +1,5 @@
var path = require('path')
module.exports = {
'config': path.resolve('config/database.json'),
}

View File

@ -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."
}

View File

@ -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"
}
}

View File

@ -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
})
})

View File

@ -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."

View File

@ -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

View File

@ -5,7 +5,7 @@
"license": "GPL-3.0",
"scripts": {
"start": "node .",
"serve": "nodemon"
"serve": "nodemon . --ignore config"
},
"dependencies": {
"argon2": "^0.28.7",

View File

@ -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)

View File

@ -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()

View File

@ -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",

View File

@ -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()

View File

@ -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) {

View File

@ -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) => {
// (?<!#\s*) Negative lookbehind to avoid matching comments (lines that starts with #).
// There is a double slash in the RegExp constructor to escape it.
// (?==) Positive lookahead to check if there is an equal sign right after the key.
// This is to prevent matching keys prefixed with the key of the env var to update.
const keyValRegex = new RegExp(`(?<!#\\s*)${key}(?==)`)
return line.match(keyValRegex)
})
)
// if key-value pair exists in the .env file,
if (target !== -1) {
// replace the key/value with the new value
ENV_VARS.splice(target, 1, `${key}=${value}`)
} else {
// if it doesn't exist, add it instead
ENV_VARS.push(`${key}=${value}`)
}
// write everything back to the file system
fs.writeFileSync("../backend/.env", ENV_VARS.join(os.EOL))
if (!fs.existsSync("../backend/config/config.json")) {
fs.writeFileSync("../backend/config/config.json", "{}")
}
if (!fs.existsSync("../backend/.env")) {
fs.writeFileSync("../backend/.env", "")
}
setEnvValue(
"HOSTNAME",
await input.text("Public Domain", {
default: "localhost"
})
)
setEnvValue(
"CORS_HOSTNAME",
await input.text("Public Hostname", {
default: "http://localhost:8080"
})
)
setEnvValue(
"SITE_NAME",
await input.text("Site Name", {
default: "Colubrina"
})
)
setEnvValue(
"ALLOW_REGISTRATIONS",
await input.text("Permit Public Registrations", {
default: false
})
)
setEnvValue(
"PUBLIC_USERS",
await input.text("Show instance users publicly", {
default: false
})
)
const emailVerify = await input.text("Enforce email verification?", {
let config = require("../backend/config/config.json")
config.hostname = await input.text("Public Domain", {
default: "localhost"
})
config.corsHostname = await input.text("CORS Hostname", {
default: "http://localhost"
})
config.siteName = await input.text("Site Name", {
default: "Colubrina"
})
config.allowRegistrations = await input.text("Allow Registrations", {
default: true
})
config.publicUsers = await input.text("Show instance users publicly?", {
default: true
})
config.emailVerify = await input.text("Enforce email verification?", {
default: false
})
if (emailVerify) {
setEnvValue("EMAIL_VERIFICATION", true)
setEnvValue(
"EMAIL_SMTP_HOST",
await input.text("SMTP Host", {
default: "smtp.myhost.com"
})
)
setEnvValue(
"EMAIL_SMTP_PORT",
await input.text("SMTP Port", {
default: 587
})
)
setEnvValue(
"EMAIL_SMTP_USER",
await input.text("SMTP User", {
default: "colubrina@myhost.com"
})
)
setEnvValue(
"EMAIL_SMTP_FROM",
await input.text("SMTP From Address", {
default: "colubrina@myhost.com"
})
)
setEnvValue("EMAIL_SMTP_PASSWORD", await input.text("SMTP Password", {}))
setEnvValue(
"EMAIL_SMTP_SECURE",
await input.text("SMTP Secure", {
default: false
})
)
if (config.emailVerify) {
config.emailSMTPHost = await input.text("SMTP Host", {
default: "mail.example.com"
})
config.emailSMTPPort = await input.text("SMTP Port", {
default: 587
})
config.emailSMTPUsername = await input.text("SMTP Username", {
default: "colubrina@example.com"
})
config.emailSMTPPassword = await input.text("SMTP Password", {})
config.emailSMTPFrom = await input.text("SMTP From Address", {
default: "colubrina@example.com"
})
config.emailSMTPSecure = await input.text("SMTP Secure", {
default: true
})
} else {
setEnvValue("EMAIL_VERIFICATION", false)
setEnvValue("EMAIL_SMTP_HOST", "smtp.myhost.com")
setEnvValue("EMAIL_SMTP_PORT", "587")
setEnvValue("EMAIL_SMTP_USER", "colubrina@myhost.com")
setEnvValue("EMAIL_SMTP_FROM", "colubrina@myhost.com")
setEnvValue("EMAIL_SMTP_PASSWORD", "myPassword")
setEnvValue("EMAIL_SMTP_SECURE", false)
config.emailSMTPHost = "smtp.myhost.com"
config.emailSMTPPort = 587
config.emailSMTPUsername = "colubrina@example.com"
config.emailSMTPFrom = "colubrina@example.com"
config.emailSMTPPassword = ""
config.emailSMTPSecure = true
}
setEnvValue("PUBLIC_USERS")
setEnvValue("NOTIFICATION", "")
setEnvValue("NOTIFICATION_TYPE", "info")
setEnvValue("RELEASE", "stable")
config.notification = ""
config.notificationType = "info"
config.release = "stable"
config.rules = "Write your instance rules here."
fs.writeFileSync(
"../backend/config/config.json",
JSON.stringify(config, null, 2)
)
}
async function init() {
while (true) {
@ -288,7 +238,7 @@ async function init() {
"First-time setup",
"Create user",
"Run migrations",
"Update/create config file",
"Update/create database config file",
"Check for updates",
"Build frontend for production",
"Exit"
@ -303,10 +253,15 @@ async function init() {
execSync("cd ../frontend && yarn install --frozen-lockfile", () => {
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") {

View File

@ -1,6 +1,6 @@
{
"name": "colubrina",
"version": "1.0.18",
"version": "1.0.19",
"private": true,
"author": "Troplo <troplo@troplo.com>",
"scripts": {

View File

@ -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")

View File

@ -32,6 +32,14 @@
label="Allow registrations"
v-model="allowRegistrations"
></v-switch>
<v-textarea ref="rules" label="Instance Rules" v-model="rules" class="mx-3">
</v-textarea>
<v-card-title>
<v-icon class="mr-1">mdi-language-markdown</v-icon>Rules Preview:
</v-card-title>
<v-card-text>
<span v-markdown class="mx-3" :key="rules">{{ rules }}</span>
</v-card-text>
<v-btn text class="mx-3 mb-3" color="primary" @click="updateState"
>Save</v-btn
>
@ -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
}
}
</script>

View File

@ -1,8 +1,38 @@
<template>
<div id="admin-users">
<v-dialog width="500" v-model="create.dialog">
<v-card color="card">
<v-toolbar color="toolbar">
<v-toolbar-title>Create User</v-toolbar-title>
</v-toolbar>
<v-container>
<v-form @submit.prevent="createUser">
<v-text-field
label="Username"
v-model="create.username"
></v-text-field>
<v-text-field label="Email" v-model="create.email"></v-text-field>
<v-text-field
label="Password"
type="password"
v-model="create.password"
></v-text-field>
<v-switch
inset
label="Email Verified?"
v-model="create.emailVerified"
></v-switch>
<v-btn text type="submit" color="primary">Create</v-btn>
</v-form>
</v-container>
</v-card>
</v-dialog>
<v-toolbar color="toolbar">
<v-toolbar-title>Users ({{ users.count }})</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn @click="create.dialog = true" icon>
<v-icon>mdi-plus</v-icon>
</v-btn>
<v-btn @click="getUsers" icon>
<v-icon>mdi-refresh</v-icon>
</v-btn>
@ -19,6 +49,21 @@
<template v-slot:item.index="{ index }">
{{ index }}
</template>
<template v-slot:item.actions="{ item }">
<v-tooltip top>
<template v-slot:activator="{ on }">
<v-btn
v-on="on"
icon
@click="banUser(item)"
:disabled="item.id === $store.state.user.id || item.admin"
>
<v-icon>mdi-gavel</v-icon>
</v-btn>
</template>
<span>Ban</span>
</v-tooltip>
</template>
</v-data-table>
</div>
</template>
@ -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")

View File

@ -30,8 +30,7 @@
<v-form ref="form" class="pa-4 pt-6">
<p class="text-center text-h4">
Login to
<span class="troplo-title">{{ $store.state.site.name }}</span
><small style="font-size: 15px">beta</small>
<span class="troplo-title">{{ $store.state.site.name }}</span>
</p>
<v-text-field
@keyup.enter="doLogin()"

View File

@ -1,5 +1,19 @@
<template>
<div id="login" v-if="!$store.state.user?.bcUser?.id">
<div id="login" v-if="!$store.state.user?.id">
<v-dialog v-model="rulesDialog" max-width="700px">
<v-card color="card">
<v-toolbar color="toolbar">
<v-toolbar-title>
{{ $store.state.site.name }} Rules
</v-toolbar-title>
</v-toolbar>
<v-card-text class="mt-3" style="color: unset">
<span v-markdown :key="$store.state.site.rules">{{
$store.state.site.rules
}}</span>
</v-card-text>
</v-card>
</v-dialog>
<div :class="{ outer: !$vuetify.breakpoint.mobile }">
<div :class="{ middle: !$vuetify.breakpoint.mobile }">
<div :class="{ innerLogin: !$vuetify.breakpoint.mobile }">
@ -8,8 +22,7 @@
<v-form ref="form" class="pa-4 pt-6">
<p class="text-center text-h4">
Register to
<span class="troplo-title">{{ $store.state.site.name }}</span
><small style="font-size: 15px">beta</small>
<span class="troplo-title">{{ $store.state.site.name }}</span>
</p>
<v-text-field
@keyup.enter="doRegister()"
@ -52,6 +65,38 @@
>This instance has email verification enforced, ensure your
email is correct.</small
>
<v-row align="center">
<v-tooltip top v-if="!rulesOpenedOnce">
<template v-slot:activator="{ on }">
<div v-on="on">
<v-switch
class="ml-4 mt-5"
inset
v-model="rules"
:disabled="!rulesOpenedOnce"
></v-switch>
</div>
</template>
<span>You need to view the rules first.</span>
</v-tooltip>
<v-switch
class="ml-4 mt-5"
inset
v-model="rules"
v-else
:disabled="!rulesOpenedOnce"
></v-switch>
I have read and agree to the&nbsp;
<a
@click="
rulesDialog = true
rulesOpenedOnce = true
"
target="_blank"
>
instance rules </a
>.
</v-row>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
@ -96,6 +141,9 @@ export default {
totp: "",
totpDialog: false,
loading: false,
rules: false,
rulesDialog: false,
rulesOpenedOnce: false,
instance:
localStorage.getItem("instance") || "https://colubrina.troplo.com",
instanceString: ""
@ -123,6 +171,12 @@ export default {
return window.innerHeight
},
doRegister() {
if (!this.rules) {
this.$toast.error(
"You need to accept the rules before you can register."
)
return
}
this.loading = true
this.axios
.post("/api/v1/user/register", {

View File

@ -94,9 +94,37 @@ module.exports = {
builderOptions: {
appId: "com.troplo.colubrina",
win: {
target: [
{
target: "nsis",
arch: ["x64", "ia32", "armv7l", "arm64"]
}
],
publish: ["github"]
},
linux: {
target: [
{
target: "AppImage",
arch: ["x64", "ia32", "armv7l", "arm64"]
},
{
target: "deb",
arch: ["x64", "ia32", "armv7l", "arm64"]
},
{
target: "snap",
arch: ["x64", "ia32", "armv7l", "arm64"]
},
{
target: "pacman",
arch: ["x64", "ia32", "armv7l", "arm64"]
},
{
target: "tar.gz",
arch: ["x64", "ia32", "armv7l", "arm64"]
}
],
publish: ["github"],
category: "Network",
synopsis: "Instant Messaging",