mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 10:34:17 +00:00
commit from local
This commit is contained in:
parent
b415f024b5
commit
c7f8d33aa6
@ -7,7 +7,7 @@
|
||||
3004 -> chats
|
||||
3005 -> marketplace
|
||||
3006 -> sync
|
||||
3007 -> mail
|
||||
3007 -> ems (External Messaging Service)
|
||||
3008 -> unallocated
|
||||
3009 -> unallocated
|
||||
3010 -> unallocated
|
||||
@ -16,3 +16,8 @@
|
||||
3013 -> unallocated
|
||||
3014 -> unallocated
|
||||
3015 -> unallocated
|
||||
3016 -> unallocated
|
||||
3017 -> unallocated
|
||||
3018 -> unallocated
|
||||
3019 -> unallocated
|
||||
3020 -> auth
|
@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
require("dotenv").config()
|
||||
require("sucrase/register")
|
||||
|
||||
const path = require("path")
|
||||
@ -28,6 +29,7 @@ global["aliases"] = {
|
||||
"@shared-utils": path.resolve(__dirname, "utils"),
|
||||
"@shared-classes": path.resolve(__dirname, "classes"),
|
||||
"@shared-lib": path.resolve(__dirname, "lib"),
|
||||
"@shared-middlewares": path.resolve(__dirname, "middlewares"),
|
||||
|
||||
// expose internal resources
|
||||
"@lib": path.resolve(global["__src"], "lib"),
|
||||
|
237
packages/server/classes/AuthToken/index.js
Normal file
237
packages/server/classes/AuthToken/index.js
Normal file
@ -0,0 +1,237 @@
|
||||
import jwt from "jsonwebtoken"
|
||||
import { Session, RegenerationToken, User } from "../../classes/DbModels"
|
||||
|
||||
export default class Token {
|
||||
static get strategy() {
|
||||
return {
|
||||
secret: process.env.JWT_SECRET,
|
||||
expiresIn: process.env.JWT_EXPIRES_IN ?? "1h",
|
||||
algorithm: process.env.JWT_ALGORITHM ?? "HS256",
|
||||
}
|
||||
}
|
||||
|
||||
static async createNewAuthToken(payload, options = {}) {
|
||||
if (options.updateSession) {
|
||||
const sessionData = await Session.findOne({ _id: options.updateSession })
|
||||
|
||||
payload.session_uuid = sessionData.session_uuid
|
||||
} else {
|
||||
payload.session_uuid = global.nanoid()
|
||||
}
|
||||
|
||||
const { secret, expiresIn, algorithm } = Token.strategy
|
||||
|
||||
const token = jwt.sign(
|
||||
{
|
||||
session_uuid: payload.session_uuid,
|
||||
username: payload.username,
|
||||
user_id: payload.user_id,
|
||||
signLocation: payload.signLocation,
|
||||
},
|
||||
secret,
|
||||
{
|
||||
expiresIn: expiresIn,
|
||||
algorithm: algorithm
|
||||
}
|
||||
)
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
static async validate(token) {
|
||||
let result = {
|
||||
expired: false,
|
||||
valid: true,
|
||||
error: null,
|
||||
data: null,
|
||||
}
|
||||
|
||||
if (typeof token === "undefined") {
|
||||
result.valid = false
|
||||
result.error = "Missing token"
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
const { secret } = Token.strategy
|
||||
|
||||
await jwt.verify(token, secret, async (err, decoded) => {
|
||||
if (err) {
|
||||
result.valid = false
|
||||
result.error = err.message
|
||||
|
||||
if (err.message === "jwt expired") {
|
||||
result.expired = true
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
result.data = decoded
|
||||
|
||||
const sessions = await Session.find({ user_id: decoded.user_id })
|
||||
const currentSession = sessions.find((session) => session.token === token)
|
||||
|
||||
if (!currentSession) {
|
||||
result.valid = false
|
||||
result.error = "Session token not found"
|
||||
} else {
|
||||
result.session = currentSession
|
||||
result.valid = true
|
||||
result.user = async () => await User.findOne({ _id: decoded.user_id })
|
||||
}
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
static async regenerate(expiredToken, refreshToken, aggregateData = {}) {
|
||||
// search for a regeneration token with the expired token (Should exist only one)
|
||||
const regenerationToken = await RegenerationToken.findOne({ refreshToken: refreshToken })
|
||||
|
||||
if (!regenerationToken) {
|
||||
throw new Error("Cannot find regeneration token")
|
||||
}
|
||||
|
||||
// check if the regeneration token is valid and not expired
|
||||
let decodedRefreshToken = null
|
||||
let decodedExpiredToken = null
|
||||
|
||||
try {
|
||||
decodedRefreshToken = jwt.decode(refreshToken)
|
||||
decodedExpiredToken = jwt.decode(expiredToken)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
// TODO: Storage this incident
|
||||
}
|
||||
|
||||
if (!decodedRefreshToken) {
|
||||
throw new Error("Cannot decode refresh token")
|
||||
}
|
||||
|
||||
if (!decodedExpiredToken) {
|
||||
throw new Error("Cannot decode expired token")
|
||||
}
|
||||
|
||||
// is not needed to verify the expired token, because it suppossed to be expired
|
||||
|
||||
// verify refresh token
|
||||
await jwt.verify(refreshToken, global.jwtStrategy.secretOrKey, async (err) => {
|
||||
// check if is expired
|
||||
if (err) {
|
||||
if (err.message === "jwt expired") {
|
||||
// check if server has enabled the enforcement of regeneration token expiration
|
||||
if (global.jwtStrategy.enforceRegenerationTokenExpiration) {
|
||||
// delete the regeneration token
|
||||
await RegenerationToken.deleteOne({ refreshToken: refreshToken })
|
||||
|
||||
throw new Error("Regeneration token expired and cannot be regenerated due server has enabled enforcement security policy")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if the regeneration token is associated with the expired token
|
||||
if (decodedRefreshToken.expiredToken !== expiredToken) {
|
||||
throw new Error("Regeneration token is not associated with the expired token")
|
||||
}
|
||||
})
|
||||
|
||||
// find the session associated with the expired token
|
||||
const session = await Session.findOne({ token: expiredToken })
|
||||
|
||||
if (!session) {
|
||||
throw new Error("Cannot find session associated with the expired token")
|
||||
}
|
||||
|
||||
// generate a new token
|
||||
const newToken = await this.createNewAuthToken({
|
||||
username: decodedExpiredToken.username,
|
||||
session_uuid: session.session_uuid,
|
||||
user_id: decodedExpiredToken.user_id,
|
||||
ip_address: aggregateData.ip_address,
|
||||
}, {
|
||||
updateSession: session._id,
|
||||
})
|
||||
|
||||
// delete the regeneration token
|
||||
await RegenerationToken.deleteOne({ refreshToken: refreshToken })
|
||||
|
||||
return newToken
|
||||
}
|
||||
|
||||
static async createAuth(payload, options = {}) {
|
||||
const token = await this.createNewAuthToken(payload, options)
|
||||
|
||||
const session = {
|
||||
token: token,
|
||||
session_uuid: payload.session_uuid,
|
||||
username: payload.username,
|
||||
user_id: payload.user_id,
|
||||
location: payload.signLocation,
|
||||
ip_address: payload.ip_address,
|
||||
client: payload.client,
|
||||
date: new Date().getTime(),
|
||||
}
|
||||
|
||||
if (options.updateSession) {
|
||||
await Session.findByIdAndUpdate(options.updateSession, session)
|
||||
} else {
|
||||
let newSession = new Session(session)
|
||||
|
||||
await newSession.save()
|
||||
}
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
static async createRegenerative(expiredToken) {
|
||||
// check if token is only expired, if is corrupted, reject
|
||||
let decoded = null
|
||||
|
||||
try {
|
||||
decoded = jwt.decode(expiredToken)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
if (!decoded) {
|
||||
return false
|
||||
}
|
||||
|
||||
// check if token exists on a session
|
||||
const sessions = await Session.find({ user_id: decoded.user_id })
|
||||
const currentSession = sessions.find((session) => session.token === expiredToken)
|
||||
|
||||
if (!currentSession) {
|
||||
throw new Error("This token is not associated with any session")
|
||||
}
|
||||
|
||||
// create a new refresh token and sign it with maximum expiration time of 1 day
|
||||
const refreshToken = jwt.sign(
|
||||
{
|
||||
expiredToken
|
||||
},
|
||||
global.jwtStrategy.secretOrKey,
|
||||
{
|
||||
expiresIn: "1d"
|
||||
}
|
||||
)
|
||||
|
||||
// create a new regeneration token and save it
|
||||
const regenerationToken = new RegenerationToken({
|
||||
expiredToken,
|
||||
refreshToken,
|
||||
})
|
||||
|
||||
await regenerationToken.save()
|
||||
|
||||
// return the regeneration token
|
||||
return regenerationToken
|
||||
}
|
||||
|
||||
static async getRegenerationToken(expiredToken) {
|
||||
const regenerationToken = await RegenerationToken.findOne({ expiredToken })
|
||||
|
||||
return regenerationToken
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ function getConnectionConfig(obj) {
|
||||
dbName: DB_NAME,
|
||||
user: DB_USER,
|
||||
pass: DB_PWD,
|
||||
maxPoolSize: 100,
|
||||
}
|
||||
|
||||
if (DB_AUTH_SOURCE) {
|
||||
|
@ -50,12 +50,7 @@ export default () => {
|
||||
|
||||
clientOptions = composeURL(clientOptions)
|
||||
|
||||
let client = {}
|
||||
|
||||
client.initialize = async () => {
|
||||
console.log(`🔌 Connecting to Redis client [${REDIS_HOST}]`)
|
||||
|
||||
client = new Redis(clientOptions)
|
||||
let client = new Redis(clientOptions.host, clientOptions.port, clientOptions)
|
||||
|
||||
client.on("error", (error) => {
|
||||
console.error("❌ Redis client error:", error)
|
||||
@ -69,8 +64,16 @@ export default () => {
|
||||
console.log("🔄 Redis client reconnecting...")
|
||||
})
|
||||
|
||||
return client
|
||||
const initialize = async () => {
|
||||
return await new Promise((resolve, reject) => {
|
||||
console.log(`🔌 Connecting to Redis client [${REDIS_HOST}]`)
|
||||
|
||||
client.connect(resolve)
|
||||
})
|
||||
}
|
||||
|
||||
return client
|
||||
return {
|
||||
client,
|
||||
initialize
|
||||
}
|
||||
}
|
133
packages/server/classes/SecureEntry/index.js
Normal file
133
packages/server/classes/SecureEntry/index.js
Normal file
@ -0,0 +1,133 @@
|
||||
import crypto from "crypto"
|
||||
|
||||
export default class SecureEntry {
|
||||
constructor(model, params = {}) {
|
||||
this.params = params
|
||||
|
||||
if (!model) {
|
||||
throw new Error("Missing model")
|
||||
}
|
||||
|
||||
this.model = model
|
||||
}
|
||||
|
||||
static get encrytionAlgorithm() {
|
||||
return "aes-256-cbc"
|
||||
}
|
||||
|
||||
async set(key, value, {
|
||||
keyName = "key",
|
||||
valueName = "value",
|
||||
}) {
|
||||
if (!keyName) {
|
||||
throw new Error("Missing keyName")
|
||||
}
|
||||
|
||||
if (!valueName) {
|
||||
throw new Error("Missing valueName")
|
||||
}
|
||||
|
||||
if (!key) {
|
||||
throw new Error("Missing key")
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
throw new Error("Missing value")
|
||||
}
|
||||
|
||||
let entry = await this.model.findOne({
|
||||
[keyName]: key,
|
||||
[valueName]: value,
|
||||
}).catch(() => null)
|
||||
|
||||
const encryptionKey = Buffer.from(process.env.SYNC_ENCRIPT_SECRET, "hex")
|
||||
const iv = crypto.randomBytes(16)
|
||||
|
||||
const cipher = crypto.createCipheriv(SecureEntry.encrytionAlgorithm, encryptionKey, iv)
|
||||
|
||||
let encryptedData
|
||||
|
||||
try {
|
||||
encryptedData = cipher.update(value)
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
encryptedData = Buffer.concat([encryptedData, cipher.final()])
|
||||
|
||||
value = iv.toString("hex") + ":" + encryptedData.toString("hex")
|
||||
|
||||
if (entry) {
|
||||
entry[valueName] = value
|
||||
|
||||
await entry.save()
|
||||
|
||||
return entry
|
||||
}
|
||||
|
||||
entry = new this.model({
|
||||
[keyName]: key,
|
||||
[valueName]: value,
|
||||
})
|
||||
|
||||
await entry.save()
|
||||
|
||||
return entry
|
||||
}
|
||||
|
||||
async get(key, value, {
|
||||
keyName = "key",
|
||||
valueName = "value",
|
||||
}) {
|
||||
if (!keyName) {
|
||||
throw new Error("Missing keyName")
|
||||
}
|
||||
if (!key) {
|
||||
throw new Error("Missing key")
|
||||
}
|
||||
|
||||
const searchQuery = {
|
||||
[keyName]: key,
|
||||
}
|
||||
|
||||
if (value) {
|
||||
searchQuery[valueName] = value
|
||||
}
|
||||
|
||||
const entry = await this.model.findOne(searchQuery).catch(() => null)
|
||||
|
||||
if (!entry || !entry[valueName]) {
|
||||
return null
|
||||
}
|
||||
|
||||
const encryptionKey = Buffer.from(process.env.SYNC_ENCRIPT_SECRET, "hex")
|
||||
|
||||
const iv = Buffer.from(entry[valueName].split(":")[0], "hex")
|
||||
const encryptedText = Buffer.from(entry[valueName].split(":")[1], "hex")
|
||||
|
||||
const decipher = crypto.createDecipheriv(SecureEntry.encrytionAlgorithm, encryptionKey, iv)
|
||||
|
||||
let decrypted = decipher.update(encryptedText)
|
||||
|
||||
decrypted = Buffer.concat([decrypted, decipher.final()])
|
||||
|
||||
return decrypted.toString()
|
||||
}
|
||||
|
||||
async deleteByID(_id) {
|
||||
if (!_id) {
|
||||
throw new Error("Missing _id")
|
||||
}
|
||||
|
||||
const entry = await this.model.findById(_id).catch(() => null)
|
||||
|
||||
if (!entry) {
|
||||
return null
|
||||
}
|
||||
|
||||
await entry.delete()
|
||||
|
||||
return entry
|
||||
}
|
||||
}
|
6
packages/server/middlewares/index.js
Normal file
6
packages/server/middlewares/index.js
Normal file
@ -0,0 +1,6 @@
|
||||
export default {
|
||||
withAuthentication: require("./withAuthentication").default,
|
||||
withOptionalAuthentication: require("./withOptionalAuthentication").default,
|
||||
onlyAdmin: require("./onlyAdmin").default,
|
||||
roles: require("./roles").default,
|
||||
}
|
11
packages/server/middlewares/onlyAdmin/index.js
Executable file
11
packages/server/middlewares/onlyAdmin/index.js
Executable file
@ -0,0 +1,11 @@
|
||||
export default (req, res, next) => {
|
||||
if (!req.auth) {
|
||||
return res.status(401).json({ error: "No authenticated" })
|
||||
}
|
||||
|
||||
if (!req.auth.user.roles.includes("admin")) {
|
||||
return res.status(403).json({ error: "To make this request it is necessary to have administrator permissions" })
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
19
packages/server/middlewares/roles/index.js
Executable file
19
packages/server/middlewares/roles/index.js
Executable file
@ -0,0 +1,19 @@
|
||||
export default (req, res, next) => {
|
||||
req.isAdmin = () => {
|
||||
if (req.user.roles.includes("admin")) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
req.hasRole = (role) => {
|
||||
if (req.user.roles.includes(role)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
78
packages/server/middlewares/withAuthentication/index.js
Executable file
78
packages/server/middlewares/withAuthentication/index.js
Executable file
@ -0,0 +1,78 @@
|
||||
import { authorizedServerTokens } from "../../classes/DbModels"
|
||||
import SecureEntry from "../../classes/SecureEntry"
|
||||
import AuthToken from "../../classes/AuthToken"
|
||||
|
||||
export default async (req, res, next) => {
|
||||
function reject(description) {
|
||||
return res.status(401).json({ error: `${description ?? "Invalid session"}` })
|
||||
}
|
||||
|
||||
try {
|
||||
const tokenAuthHeader = req.headers?.authorization?.split(" ")
|
||||
|
||||
if (!tokenAuthHeader) {
|
||||
return reject("Missing token header")
|
||||
}
|
||||
|
||||
if (!tokenAuthHeader[1]) {
|
||||
return reject("Recived header, missing token")
|
||||
}
|
||||
|
||||
switch (tokenAuthHeader[0]) {
|
||||
case "Bearer": {
|
||||
const token = tokenAuthHeader[1]
|
||||
|
||||
const validation = await AuthToken.validate(token)
|
||||
|
||||
if (!validation.valid) {
|
||||
return reject(validation.error)
|
||||
}
|
||||
|
||||
req.auth = {
|
||||
token: token,
|
||||
decoded: validation.data,
|
||||
session: validation.session,
|
||||
user: validation.user
|
||||
}
|
||||
|
||||
return next()
|
||||
}
|
||||
case "Server": {
|
||||
const [client_id, token] = tokenAuthHeader[1].split(":")
|
||||
|
||||
if (client_id === "undefined" || token === "undefined") {
|
||||
return reject("Invalid server token")
|
||||
}
|
||||
|
||||
const secureEntries = new SecureEntry(authorizedServerTokens)
|
||||
|
||||
const serverTokenEntry = await secureEntries.get(client_id, undefined, {
|
||||
keyName: "client_id",
|
||||
valueName: "token",
|
||||
})
|
||||
|
||||
if (!serverTokenEntry) {
|
||||
return reject("Invalid server token")
|
||||
}
|
||||
|
||||
if (serverTokenEntry !== token) {
|
||||
return reject("Missmatching server token")
|
||||
}
|
||||
|
||||
req.user = {
|
||||
__server: true,
|
||||
_id: client_id,
|
||||
roles: ["server"],
|
||||
}
|
||||
|
||||
return next()
|
||||
}
|
||||
default: {
|
||||
return reject("Invalid token type")
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return res.status(500).json({ error: "An error occurred meanwhile authenticating your token" })
|
||||
}
|
||||
}
|
9
packages/server/middlewares/withOptionalAuthentication/index.js
Executable file
9
packages/server/middlewares/withOptionalAuthentication/index.js
Executable file
@ -0,0 +1,9 @@
|
||||
import withAuthentication from "../withAuthentication"
|
||||
|
||||
export default (req, res, next) => {
|
||||
if (req.headers?.authorization) {
|
||||
withAuthentication(req, res, next)
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@
|
||||
"dotenv": "^16.4.4",
|
||||
"http-proxy-middleware": "^2.0.6",
|
||||
"hyper-express": "^6.14.12",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"linebridge": "^0.16.0",
|
||||
"module-alias": "^2.2.3",
|
||||
"p-map": "^4.0.0",
|
||||
|
25
packages/server/services/auth/auth.service.js
Normal file
25
packages/server/services/auth/auth.service.js
Normal file
@ -0,0 +1,25 @@
|
||||
import { Server } from "linebridge/src/server"
|
||||
import DbManager from "@shared-classes/DbManager"
|
||||
|
||||
import SharedMiddlewares from "@shared-middlewares"
|
||||
|
||||
export default class API extends Server {
|
||||
static refName = "auth"
|
||||
static useEngine = "hyper-express"
|
||||
static routesPath = `${__dirname}/routes`
|
||||
static listen_port = process.env.HTTP_LISTEN_PORT ?? 3020
|
||||
|
||||
middlewares = {
|
||||
...SharedMiddlewares
|
||||
}
|
||||
|
||||
contexts = {
|
||||
db: new DbManager(),
|
||||
}
|
||||
|
||||
async onInitialize() {
|
||||
await this.contexts.db.initialize()
|
||||
}
|
||||
}
|
||||
|
||||
Boot(API)
|
6
packages/server/services/auth/package.json
Normal file
6
packages/server/services/auth/package.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "auth",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT"
|
||||
}
|
22
packages/server/services/auth/routes/auth/delete.js
Normal file
22
packages/server/services/auth/routes/auth/delete.js
Normal file
@ -0,0 +1,22 @@
|
||||
import { Session } from "@shared-classes/DbModels"
|
||||
|
||||
export default {
|
||||
middlewares: ["withAuthentication"],
|
||||
fn: async (req) => {
|
||||
const { token, session } = req.auth
|
||||
|
||||
const deletedSession = await Session.findOneAndDelete({
|
||||
user_id: session.user_id,
|
||||
token: token,
|
||||
})
|
||||
|
||||
if (session) {
|
||||
return {
|
||||
message: "Session deleted",
|
||||
session: deletedSession
|
||||
}
|
||||
}
|
||||
|
||||
throw new OperationError(404, "Session not found")
|
||||
}
|
||||
}
|
36
packages/server/services/auth/routes/auth/post.js
Normal file
36
packages/server/services/auth/routes/auth/post.js
Normal file
@ -0,0 +1,36 @@
|
||||
import AuthToken from "@shared-classes/AuthToken"
|
||||
import { User } from "@shared-classes/DbModels"
|
||||
import requiredFields from "@shared-utils/requiredFields"
|
||||
import bcrypt from "bcrypt"
|
||||
|
||||
export default async (req, res) => {
|
||||
requiredFields(["username", "password"], req.body)
|
||||
|
||||
const { username, password } = req.body
|
||||
|
||||
let isEmail = username.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)
|
||||
|
||||
let query = isEmail ? { email: username } : { username: username }
|
||||
|
||||
const user = await User.findOne(query).select("+password")
|
||||
|
||||
if (!user) {
|
||||
throw new OperationError(401, "User not found")
|
||||
}
|
||||
|
||||
if (!bcrypt.compareSync(password, user.password)) {
|
||||
return res.status(401).json({
|
||||
message: "Invalid credentials",
|
||||
})
|
||||
}
|
||||
|
||||
const token = await AuthToken.createAuth({
|
||||
username: user.username,
|
||||
user_id: user._id.toString(),
|
||||
ip_address: req.headers["x-forwarded-for"]?.split(",")[0] ?? req.socket?.remoteAddress ?? req.ip,
|
||||
client: req.headers["user-agent"],
|
||||
//signLocation: global.signLocation,
|
||||
})
|
||||
|
||||
return { token: token }
|
||||
}
|
30
packages/server/services/auth/routes/availability/get.js
Normal file
30
packages/server/services/auth/routes/availability/get.js
Normal file
@ -0,0 +1,30 @@
|
||||
import { User } from "@shared-classes/DbModels"
|
||||
|
||||
export default async (req) => {
|
||||
const { username, email } = req.query
|
||||
|
||||
if (!username && !email) {
|
||||
throw new OperationError(400, "Missing username or email")
|
||||
}
|
||||
|
||||
const user = await User.findOne({
|
||||
$or: [
|
||||
{ username: username },
|
||||
{ email: email },
|
||||
]
|
||||
}).catch((error) => {
|
||||
return false
|
||||
})
|
||||
|
||||
if (user) {
|
||||
return {
|
||||
message: "User already exists",
|
||||
exists: true,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
message: "User doesn't exists",
|
||||
exists: false,
|
||||
}
|
||||
}
|
||||
}
|
72
packages/server/services/auth/routes/register/post.js
Normal file
72
packages/server/services/auth/routes/register/post.js
Normal file
@ -0,0 +1,72 @@
|
||||
import { User } from "@shared-classes/DbModels"
|
||||
import bcrypt from "bcrypt"
|
||||
|
||||
import requiredFields from "@shared-utils/requiredFields"
|
||||
|
||||
export default async (req) => {
|
||||
requiredFields(["username", "password", "email"], req.body)
|
||||
|
||||
let { username, password, email, fullName, roles, avatar, acceptTos } = req.body
|
||||
|
||||
if (ToBoolean(acceptTos) !== true) {
|
||||
throw new OperationError(400, "You must accept the terms of service in order to create an account.")
|
||||
}
|
||||
|
||||
if (username.length < 3) {
|
||||
throw new OperationError(400, "Username must be at least 3 characters")
|
||||
}
|
||||
|
||||
if (username.length > 64) {
|
||||
throw new OperationError(400, "Username cannot be longer than 64 characters")
|
||||
}
|
||||
|
||||
// if username has capital letters, throw error
|
||||
if (username !== username.toLowerCase()) {
|
||||
throw new OperationError(400, "Username must be lowercase")
|
||||
}
|
||||
|
||||
// make sure the username has no spaces
|
||||
if (username.includes(" ")) {
|
||||
throw new OperationError(400, "Username cannot contain spaces")
|
||||
}
|
||||
|
||||
// make sure the username has no valid characters. Only letters, numbers, and underscores
|
||||
if (!/^[a-z0-9_]+$/.test(username)) {
|
||||
throw new OperationError(400, "Username can only contain letters, numbers, and underscores")
|
||||
}
|
||||
|
||||
// check if username is already taken
|
||||
const existentUser = await User.findOne({ username: username })
|
||||
|
||||
if (existentUser) {
|
||||
throw new OperationError(400, "User already exists")
|
||||
}
|
||||
|
||||
// check if the email is already in use
|
||||
const existentEmail = await User.findOne({ email: email })
|
||||
|
||||
if (existentEmail) {
|
||||
throw new OperationError(400, "Email already in use")
|
||||
}
|
||||
|
||||
// hash the password
|
||||
const hash = bcrypt.hashSync(password, parseInt(process.env.BCRYPT_ROUNDS ?? 3))
|
||||
|
||||
let user = new User({
|
||||
username: username,
|
||||
password: hash,
|
||||
email: email,
|
||||
fullName: fullName,
|
||||
avatar: avatar ?? `https://api.dicebear.com/7.x/thumbs/svg?seed=${username}`,
|
||||
roles: roles,
|
||||
createdAt: new Date().getTime(),
|
||||
acceptTos: acceptTos,
|
||||
})
|
||||
|
||||
await user.save()
|
||||
|
||||
// TODO: dispatch event bus
|
||||
//global.eventBus.emit("user.create", user)
|
||||
|
||||
return user
|
||||
}
|
@ -21,7 +21,7 @@ export default class API {
|
||||
|
||||
this.options = {
|
||||
listenHost: process.env.HTTP_LISTEN_HOST || "0.0.0.0",
|
||||
listenPort: process.env.HTTP_LISTEN_PORT || 3020,
|
||||
listenPort: process.env.HTTP_LISTEN_PORT || 3004,
|
||||
...options
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ export default class FileServerAPI {
|
||||
server = global.server = express()
|
||||
|
||||
listenIp = process.env.HTTP_LISTEN_IP ?? "0.0.0.0"
|
||||
listenPort = process.env.HTTP_LISTEN_PORT ?? 3060
|
||||
listenPort = process.env.HTTP_LISTEN_PORT ?? 3002
|
||||
|
||||
redis = global.redis = RedisClient()
|
||||
|
||||
|
@ -8,7 +8,7 @@ import Token from "@lib/token"
|
||||
|
||||
export default class API extends Server {
|
||||
static refName = "MAIN-API"
|
||||
static listen_port = process.env.HTTP_LISTEN_PORT || 3010
|
||||
static listen_port = process.env.HTTP_LISTEN_PORT || 3000
|
||||
static requireWSAuth = true
|
||||
|
||||
constructor(params) {
|
||||
|
@ -28,7 +28,6 @@
|
||||
"nsfwjs": "^3.0.0",
|
||||
"p-map": "^4.0.0",
|
||||
"p-queue": "^7.3.4",
|
||||
"path-to-regexp": "^6.2.1",
|
||||
"sharp": "^0.33.2"
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ export default class API {
|
||||
server = global.server = new hyperexpress.Server()
|
||||
|
||||
listenIp = process.env.HTTP_LISTEN_IP ?? "0.0.0.0"
|
||||
listenPort = process.env.HTTP_LISTEN_PORT ?? 3040
|
||||
listenPort = process.env.HTTP_LISTEN_PORT ?? 3005
|
||||
|
||||
internalRouter = new hyperexpress.Router()
|
||||
|
||||
|
@ -28,7 +28,7 @@ export default class API {
|
||||
|
||||
this.options = {
|
||||
listenHost: process.env.HTTP_LISTEN_IP ?? "0.0.0.0",
|
||||
listenPort: process.env.HTTP_LISTEN_PORT ?? 3050,
|
||||
listenPort: process.env.HTTP_LISTEN_PORT ?? 3003,
|
||||
...options
|
||||
}
|
||||
}
|
||||
|
@ -10,8 +10,6 @@ export default async (payload) => {
|
||||
posts = [posts]
|
||||
}
|
||||
|
||||
console.log(posts, posts.every((post) => !post))
|
||||
|
||||
if (posts.every((post) => !post)) {
|
||||
return []
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
import { Server } from "linebridge/src/server"
|
||||
|
||||
import DbManager from "@shared-classes/DbManager"
|
||||
import RedisClient from "@shared-classes/RedisClient"
|
||||
|
||||
import SharedMiddlewares from "@shared-middlewares"
|
||||
|
||||
export default class API extends Server {
|
||||
static refName = "posts"
|
||||
@ -8,8 +11,13 @@ export default class API extends Server {
|
||||
static routesPath = `${__dirname}/routes`
|
||||
static listen_port = process.env.HTTP_LISTEN_PORT ?? 3001
|
||||
|
||||
middlewares = {
|
||||
...SharedMiddlewares
|
||||
}
|
||||
|
||||
contexts = {
|
||||
db: new DbManager(),
|
||||
redis: RedisClient()
|
||||
}
|
||||
|
||||
events = {
|
||||
@ -18,6 +26,7 @@ export default class API extends Server {
|
||||
|
||||
async onInitialize() {
|
||||
await this.contexts.db.initialize()
|
||||
await this.contexts.redis.initialize()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ export default {
|
||||
fn: async (req, res) => {
|
||||
const result = await Posts.data({
|
||||
post_id: req.params.post_id,
|
||||
for_user_id: req.user?._id.toString(),
|
||||
for_user_id: req.auth?.session?.user_id,
|
||||
})
|
||||
|
||||
return result
|
||||
|
@ -15,7 +15,7 @@ export default class API {
|
||||
server = global.server = new hyperexpress.Server()
|
||||
|
||||
listenIp = process.env.HTTP_LISTEN_IP ?? "0.0.0.0"
|
||||
listenPort = process.env.HTTP_LISTEN_PORT ?? 3070
|
||||
listenPort = process.env.HTTP_LISTEN_PORT ?? 3006
|
||||
|
||||
internalRouter = new hyperexpress.Router()
|
||||
|
||||
|
@ -17,6 +17,6 @@ export default (fields, obj) => {
|
||||
}
|
||||
|
||||
if (missing.length > 0) {
|
||||
throw new Error(`Missing required fields: ${missing.join(", ")}`)
|
||||
throw new OperationError(400, `Missing required fields: ${missing.join(", ")}`)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user