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
|
3004 -> chats
|
||||||
3005 -> marketplace
|
3005 -> marketplace
|
||||||
3006 -> sync
|
3006 -> sync
|
||||||
3007 -> mail
|
3007 -> ems (External Messaging Service)
|
||||||
3008 -> unallocated
|
3008 -> unallocated
|
||||||
3009 -> unallocated
|
3009 -> unallocated
|
||||||
3010 -> unallocated
|
3010 -> unallocated
|
||||||
@ -16,3 +16,8 @@
|
|||||||
3013 -> unallocated
|
3013 -> unallocated
|
||||||
3014 -> unallocated
|
3014 -> unallocated
|
||||||
3015 -> unallocated
|
3015 -> unallocated
|
||||||
|
3016 -> unallocated
|
||||||
|
3017 -> unallocated
|
||||||
|
3018 -> unallocated
|
||||||
|
3019 -> unallocated
|
||||||
|
3020 -> auth
|
@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
require("dotenv").config()
|
||||||
require("sucrase/register")
|
require("sucrase/register")
|
||||||
|
|
||||||
const path = require("path")
|
const path = require("path")
|
||||||
@ -28,6 +29,7 @@ global["aliases"] = {
|
|||||||
"@shared-utils": path.resolve(__dirname, "utils"),
|
"@shared-utils": path.resolve(__dirname, "utils"),
|
||||||
"@shared-classes": path.resolve(__dirname, "classes"),
|
"@shared-classes": path.resolve(__dirname, "classes"),
|
||||||
"@shared-lib": path.resolve(__dirname, "lib"),
|
"@shared-lib": path.resolve(__dirname, "lib"),
|
||||||
|
"@shared-middlewares": path.resolve(__dirname, "middlewares"),
|
||||||
|
|
||||||
// expose internal resources
|
// expose internal resources
|
||||||
"@lib": path.resolve(global["__src"], "lib"),
|
"@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,
|
dbName: DB_NAME,
|
||||||
user: DB_USER,
|
user: DB_USER,
|
||||||
pass: DB_PWD,
|
pass: DB_PWD,
|
||||||
|
maxPoolSize: 100,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DB_AUTH_SOURCE) {
|
if (DB_AUTH_SOURCE) {
|
||||||
|
@ -50,27 +50,30 @@ export default () => {
|
|||||||
|
|
||||||
clientOptions = composeURL(clientOptions)
|
clientOptions = composeURL(clientOptions)
|
||||||
|
|
||||||
let client = {}
|
let client = new Redis(clientOptions.host, clientOptions.port, clientOptions)
|
||||||
|
|
||||||
client.initialize = async () => {
|
client.on("error", (error) => {
|
||||||
console.log(`🔌 Connecting to Redis client [${REDIS_HOST}]`)
|
console.error("❌ Redis client error:", error)
|
||||||
|
})
|
||||||
|
|
||||||
client = new Redis(clientOptions)
|
client.on("connect", () => {
|
||||||
|
console.log(`✅ Redis client connected [${process.env.REDIS_HOST}]`)
|
||||||
|
})
|
||||||
|
|
||||||
client.on("error", (error) => {
|
client.on("reconnecting", () => {
|
||||||
console.error("❌ Redis client error:", error)
|
console.log("🔄 Redis client reconnecting...")
|
||||||
|
})
|
||||||
|
|
||||||
|
const initialize = async () => {
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
console.log(`🔌 Connecting to Redis client [${REDIS_HOST}]`)
|
||||||
|
|
||||||
|
client.connect(resolve)
|
||||||
})
|
})
|
||||||
|
|
||||||
client.on("connect", () => {
|
|
||||||
console.log(`✅ Redis client connected [${process.env.REDIS_HOST}]`)
|
|
||||||
})
|
|
||||||
|
|
||||||
client.on("reconnecting", () => {
|
|
||||||
console.log("🔄 Redis client reconnecting...")
|
|
||||||
})
|
|
||||||
|
|
||||||
return client
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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",
|
"dotenv": "^16.4.4",
|
||||||
"http-proxy-middleware": "^2.0.6",
|
"http-proxy-middleware": "^2.0.6",
|
||||||
"hyper-express": "^6.14.12",
|
"hyper-express": "^6.14.12",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
"linebridge": "^0.16.0",
|
"linebridge": "^0.16.0",
|
||||||
"module-alias": "^2.2.3",
|
"module-alias": "^2.2.3",
|
||||||
"p-map": "^4.0.0",
|
"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 = {
|
this.options = {
|
||||||
listenHost: process.env.HTTP_LISTEN_HOST || "0.0.0.0",
|
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
|
...options
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ export default class FileServerAPI {
|
|||||||
server = global.server = express()
|
server = global.server = express()
|
||||||
|
|
||||||
listenIp = process.env.HTTP_LISTEN_IP ?? "0.0.0.0"
|
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()
|
redis = global.redis = RedisClient()
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import Token from "@lib/token"
|
|||||||
|
|
||||||
export default class API extends Server {
|
export default class API extends Server {
|
||||||
static refName = "MAIN-API"
|
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
|
static requireWSAuth = true
|
||||||
|
|
||||||
constructor(params) {
|
constructor(params) {
|
||||||
|
@ -28,7 +28,6 @@
|
|||||||
"nsfwjs": "^3.0.0",
|
"nsfwjs": "^3.0.0",
|
||||||
"p-map": "^4.0.0",
|
"p-map": "^4.0.0",
|
||||||
"p-queue": "^7.3.4",
|
"p-queue": "^7.3.4",
|
||||||
"path-to-regexp": "^6.2.1",
|
|
||||||
"sharp": "^0.33.2"
|
"sharp": "^0.33.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ export default class API {
|
|||||||
server = global.server = new hyperexpress.Server()
|
server = global.server = new hyperexpress.Server()
|
||||||
|
|
||||||
listenIp = process.env.HTTP_LISTEN_IP ?? "0.0.0.0"
|
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()
|
internalRouter = new hyperexpress.Router()
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ export default class API {
|
|||||||
|
|
||||||
this.options = {
|
this.options = {
|
||||||
listenHost: process.env.HTTP_LISTEN_IP ?? "0.0.0.0",
|
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
|
...options
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,6 @@ export default async (payload) => {
|
|||||||
posts = [posts]
|
posts = [posts]
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(posts, posts.every((post) => !post))
|
|
||||||
|
|
||||||
if (posts.every((post) => !post)) {
|
if (posts.every((post) => !post)) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import { Server } from "linebridge/src/server"
|
import { Server } from "linebridge/src/server"
|
||||||
|
|
||||||
import DbManager from "@shared-classes/DbManager"
|
import DbManager from "@shared-classes/DbManager"
|
||||||
|
import RedisClient from "@shared-classes/RedisClient"
|
||||||
|
|
||||||
|
import SharedMiddlewares from "@shared-middlewares"
|
||||||
|
|
||||||
export default class API extends Server {
|
export default class API extends Server {
|
||||||
static refName = "posts"
|
static refName = "posts"
|
||||||
@ -8,8 +11,13 @@ export default class API extends Server {
|
|||||||
static routesPath = `${__dirname}/routes`
|
static routesPath = `${__dirname}/routes`
|
||||||
static listen_port = process.env.HTTP_LISTEN_PORT ?? 3001
|
static listen_port = process.env.HTTP_LISTEN_PORT ?? 3001
|
||||||
|
|
||||||
|
middlewares = {
|
||||||
|
...SharedMiddlewares
|
||||||
|
}
|
||||||
|
|
||||||
contexts = {
|
contexts = {
|
||||||
db: new DbManager(),
|
db: new DbManager(),
|
||||||
|
redis: RedisClient()
|
||||||
}
|
}
|
||||||
|
|
||||||
events = {
|
events = {
|
||||||
@ -18,6 +26,7 @@ export default class API extends Server {
|
|||||||
|
|
||||||
async onInitialize() {
|
async onInitialize() {
|
||||||
await this.contexts.db.initialize()
|
await this.contexts.db.initialize()
|
||||||
|
await this.contexts.redis.initialize()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,9 +3,9 @@ import Posts from "@classes/posts"
|
|||||||
export default {
|
export default {
|
||||||
middlewares: ["withOptionalAuthentication"],
|
middlewares: ["withOptionalAuthentication"],
|
||||||
fn: async (req, res) => {
|
fn: async (req, res) => {
|
||||||
const result = await Posts.data({
|
const result = await Posts.data({
|
||||||
post_id: req.params.post_id,
|
post_id: req.params.post_id,
|
||||||
for_user_id: req.user?._id.toString(),
|
for_user_id: req.auth?.session?.user_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
@ -15,7 +15,7 @@ export default class API {
|
|||||||
server = global.server = new hyperexpress.Server()
|
server = global.server = new hyperexpress.Server()
|
||||||
|
|
||||||
listenIp = process.env.HTTP_LISTEN_IP ?? "0.0.0.0"
|
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()
|
internalRouter = new hyperexpress.Router()
|
||||||
|
|
||||||
|
@ -17,6 +17,6 @@ export default (fields, obj) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (missing.length > 0) {
|
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