rewrite for linebridge 0.15

This commit is contained in:
SrGooglo 2023-02-13 13:27:47 +00:00
parent 8149889587
commit 82dc237edf
74 changed files with 492 additions and 357 deletions

View File

@ -21,7 +21,7 @@
"formidable": "^2.1.1",
"jimp": "^0.16.2",
"jsonwebtoken": "^9.0.0",
"linebridge": "0.15.2",
"linebridge": "0.15.4",
"luxon": "^3.2.1",
"minio": "^7.0.32",
"moment": "^2.29.4",

View File

@ -47,7 +47,7 @@ export default class API {
jwtStrategy = global.jwtStrategy = {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: this.server.oskid,
secretOrKey: this.server.server_token,
algorithms: ["sha1", "RS256", "HS256"],
expiresIn: process.env.signLifetime ?? "1h",
enforceRegenerationTokenExpiration: false,

View File

@ -2,9 +2,11 @@ import { User, Badge } from "@models"
export default {
method: "GET",
route: "/user",
route: "/user/:user_id",
fn: async (req, res) => {
const user = await User.findOne({ _id: req.query.user_id ?? req.user._id })
const user = await User.findOne({
_id: req.params.user_id,
})
if (!user) {
return res.status(404).json({ error: "User not found" })

View File

@ -1,9 +1,10 @@
import { Schematized } from "@lib"
import newComment from "../methods/newComment"
import newComment from "../services/newComment"
export default {
method: "POST",
route: "/post/:post_id",
middlewares: ["withAuthentication"],
fn: Schematized({
required: ["message"],
select: ["message"],

View File

@ -1,4 +1,4 @@
import deleteComment from "../methods/deleteComment"
import deleteComment from "../services/deleteComment"
export default {
method: "DELETE",

View File

@ -1,4 +1,4 @@
import getComments from "../methods/getComments"
import getComments from "../services/getComments"
export default {
method: "GET",

View File

@ -26,8 +26,8 @@ export default async (payload) => {
await comment.delete()
global.wsInterface.io.emit(`comment.delete.${comment_id}`)
global.wsInterface.io.emit(`post.delete.comment.${comment.parent_id.toString()}`, comment_id)
global.websocket_instance.io.emit(`comment.delete.${comment_id}`)
global.websocket_instance.io.emit(`post.delete.comment.${comment.parent_id.toString()}`, comment_id)
return comment
}

View File

@ -25,12 +25,12 @@ export default async (payload) => {
const userData = await User.findById(user_id)
global.wsInterface.io.emit(`comment.new.${parent_id}`, {
global.websocket_instance.io.emit(`comment.new.${parent_id}`, {
...comment.toObject(),
user: userData.toObject(),
})
global.wsInterface.io.emit(`post.new.comment.${parent_id}`, {
global.websocket_instance.io.emit(`post.new.comment.${parent_id}`, {
...comment.toObject(),
user: userData.toObject(),
})

View File

@ -2,7 +2,7 @@ import { UserFollow } from "@models"
export default {
method: "GET",
route: "/user/:user_id/is_followed",
route: "/user/:user_id",
middlewares: ["withAuthentication"],
fn: async (req, res) => {
const isFollowed = await UserFollow.findOne({

View File

@ -1,4 +1,4 @@
import { User, UserFollow } from "@lib"
import { User, UserFollow } from "@models"
export default {
method: "GET",

View File

@ -30,10 +30,10 @@ export default async (payload) => {
await newFollow.save()
global.wsInterface.io.emit(`user.follow`, {
global.websocket_instance.io.emit(`user.follow`, {
...user.toObject(),
})
global.wsInterface.io.emit(`user.follow.${payload.user_id}`, {
global.websocket_instance.io.emit(`user.follow.${payload.user_id}`, {
...user.toObject(),
})

View File

@ -25,10 +25,10 @@ export default async (payload) => {
await follow.remove()
global.wsInterface.io.emit(`user.unfollow`, {
global.websocket_instance.io.emit(`user.unfollow`, {
...user.toObject(),
})
global.wsInterface.io.emit(`user.unfollow.${payload.user_id}`, {
global.websocket_instance.io.emit(`user.unfollow.${payload.user_id}`, {
...user.toObject(),
})

View File

@ -1,5 +1,5 @@
import { Schematized } from "@lib"
import { CreatePost } from "../methods"
import { CreatePost } from "../services"
export default {
method: "POST",

View File

@ -1,4 +1,4 @@
import { DeletePost } from "../methods"
import { DeletePost } from "../services"
export default {
method: "DELETE",

View File

@ -1,5 +1,5 @@
import { Schematized } from "@lib"
import { GetPostData } from "../methods"
import { GetPostData } from "../services"
export default {
method: "GET",

View File

@ -1,8 +1,8 @@
import { GetPostData } from "../methods"
import { GetPostData } from "../services"
export default {
method: "GET",
route: "/:post_id",
route: "/post/:post_id",
middlewares: ["withOptionalAuthentication"],
fn: async (req, res) => {
let post = await GetPostData({

View File

@ -1,4 +1,4 @@
import { GetPostData } from "../methods"
import { GetPostData } from "../services"
export default {
method: "GET",

View File

@ -1,5 +1,5 @@
import { Schematized } from "@lib"
import { GetPostData } from "../methods"
import { GetPostData } from "../services"
export default {
method: "GET",

View File

@ -1,8 +1,8 @@
import { Schematized } from "@lib"
import { ToogleLike } from "../methods"
import { ToogleLike } from "../services"
export default {
method: "GET",
method: "POST",
route: "/:post_id/toogle_like",
middlewares: ["withAuthentication"],
fn: Schematized({

View File

@ -1,4 +1,4 @@
import { ToogleSavePost } from "../methods"
import { ToogleSavePost } from "../services"
export default {
method: "POST",

View File

@ -36,8 +36,8 @@ export default async (payload) => {
const resultPost = await getPostData({ post_id: post._id.toString() })
global.wsInterface.io.emit(`post.new`, resultPost)
global.wsInterface.io.emit(`post.new.${post.user_id}`, resultPost)
global.websocket_instance.io.emit(`post.new`, resultPost)
global.websocket_instance.io.emit(`post.new.${post.user_id}`, resultPost)
// push to background job to check if is NSFW
flagNsfwByAttachments(post._id.toString())

View File

@ -27,7 +27,7 @@ export default async (payload) => {
}
await post.remove()
global.wsInterface.io.emit(`post.delete`, post_id)
global.websocket_instance.io.emit(`post.delete`, post_id)
return post.toObject()
}

View File

@ -1,5 +1,5 @@
import { Post, User, Comment, SavedPost } from "../../../models"
import fullfillPostsData from "../../../utils/fullfillPostsData"
import { Post, SavedPost } from "@models"
import fullfillPostsData from "@utils/fullfillPostsData"
export default async (payload) => {
let {

View File

@ -24,8 +24,8 @@ export default async (post_id, modification) => {
}
}
global.wsInterface.io.emit(`post.dataUpdate`, post)
global.wsInterface.io.emit(`post.dataUpdate.${post_id._id}`, post)
global.websocket_instance.io.emit(`post.dataUpdate`, post)
global.websocket_instance.io.emit(`post.dataUpdate.${post_id._id}`, post)
return post
}

View File

@ -23,9 +23,9 @@ export default async (payload) => {
post = await modifyPostData(post._id, { likes: post.likes })
global.wsInterface.io.emit(`post.${to ? "like" : "unlike"}`, post)
global.wsInterface.io.emit(`post.${to ? "like" : "unlike"}.${post.user_id}`, post)
global.wsInterface.io.emit(`post.${to ? "like" : "unlike"}.${post_id}`, post)
global.websocket_instance.io.emit(`post.${to ? "like" : "unlike"}`, post)
global.websocket_instance.io.emit(`post.${to ? "like" : "unlike"}.${post.user_id}`, post)
global.websocket_instance.io.emit(`post.${to ? "like" : "unlike"}.${post_id}`, post)
return post
}

View File

@ -15,19 +15,19 @@ export default class RolesController extends Controller {
return res.json(roles)
}),
"/user_roles": {
"/roles/self": {
middlewares: ["withAuthentication"],
fn: Schematized({
select: ["username"],
}, async (req, res) => {
const user = await User.findOne(req.selection)
fn: async (req, res) => {
const user = await User.findOne({
_id: req.user._id.toString(),
})
if (!user) {
return res.status(404).json({ error: "No user founded" })
}
return res.json(user.roles)
})
}
},
},

View File

@ -2,14 +2,14 @@ import { Session } from "@models"
export default {
method: "DELETE",
route: "/",
route: "/current",
middlewares: ["withAuthentication"],
fn: async (req, res) => {
const { token } = req.body
const token = req.jwtToken
const user_id = req.user._id.toString()
if (typeof token === "undefined") {
return res.status(400).json("No token provided")
return res.status(400).json("Cannot access to token")
}
const session = await Session.findOneAndDelete({ user_id, token })

View File

@ -0,0 +1,16 @@
import { Endpoint } from "linebridge/dist/server"
import getConnectedUsersFollowing from "../services/getConnectedUsersFollowing"
export default class GetConnectedFollowedUsers extends Endpoint {
static method = "GET"
static route = "/connected/following"
static middlewares = ["withAuthentication"]
async fn(req, res) {
const users = await getConnectedUsersFollowing({
from_user_id: req.user._id.toString(),
})
return res.json(users)
}
}

View File

@ -0,0 +1,9 @@
import { Controller } from "linebridge/dist/server"
import generateEndpointsFromDir from "linebridge/dist/server/lib/generateEndpointsFromDir"
export default class StatusController extends Controller {
static refName = "StatusController"
static useRoute = "/status"
httpEndpoints = generateEndpointsFromDir(__dirname + "/endpoints")
}

View File

@ -12,7 +12,7 @@ export default async (payload = {}) => {
const connectedUsers = []
following.forEach((follow) => {
const connectedClient = global.wsInterface.clients.find((client) => {
const connectedClient = global.websocket_instance.clients.find((client) => {
return client.user_id === follow.to
})

View File

@ -1,11 +1,11 @@
export default {
method: "GET",
route: "/stream/addresses",
route: "/streaming/addresses",
middlewares: ["withOptionalAuthentication"],
fn: async (req, res) => {
const addresses = {
api: process.env.STREAMING_INGEST_SERVER,
ingest: process.env.STREAMING_API_SERVER,
api: process.env.STREAMING_API_SERVER,
ingest: process.env.STREAMING_INGEST_SERVER,
}
if (req.user) {

View File

@ -14,11 +14,11 @@ export default {
}
if (!user_id) {
user_id = await User.findOne({
const user = await User.findOne({
username: req.query.username,
})
user_id = user_id["_id"].toString()
user_id = user._id.toString()
}
let info = await StreamingInfo.findOne({
@ -33,19 +33,6 @@ export default {
await info.save()
}
const category = await StreamingCategory.findOne({
key: info.category
}).catch((err) => {
console.error(err)
return {}
}) ?? {}
return res.json({
...info.toObject(),
["category"]: {
key: category?.key ?? "unknown",
label: category?.label ?? "Unknown",
}
})
return res.json(info.toObject())
}
}

View File

@ -6,6 +6,12 @@ export default {
fn: async (req, res) => {
const categories = await StreamingCategory.find()
if (req.query.key) {
const category = categories.find((category) => category.key === req.query.key)
return res.json(category)
}
return res.json(categories)
}
}

View File

@ -6,6 +6,18 @@ export default {
fn: async (req, res) => {
const remoteStreams = await fetchStreamsFromAPI()
if (req.query.username) {
const stream = remoteStreams.find((stream) => stream.username === req.query.username)
if (!stream) {
return res.status(404).json({
error: "Stream not found"
})
}
return res.json(stream)
}
return res.json(remoteStreams)
}
}

View File

@ -19,9 +19,9 @@ export default {
})
if (streaming) {
global.wsInterface.io.emit(`streaming.new`, streaming)
global.websocket_instance.io.emit(`streaming.new`, streaming)
global.wsInterface.io.emit(`streaming.new.${streaming.username}`, streaming)
global.websocket_instance.io.emit(`streaming.new.${streaming.username}`, streaming)
return res.json({
code: 0,

View File

@ -13,9 +13,9 @@ export default {
})
if (streaming) {
global.wsInterface.io.emit(`streaming.end`, streaming)
global.websocket_instance.io.emit(`streaming.end`, streaming)
global.wsInterface.io.emit(`streaming.end.${streaming.username}`, streaming)
global.websocket_instance.io.emit(`streaming.end.${streaming.username}`, streaming)
return res.json({
code: 0,

View File

@ -1,7 +1,7 @@
import axios from "axios"
import lodash from "lodash"
import { StreamingCategory } from "@models"
import { StreamingCategory, StreamingInfo } from "@models"
import generateStreamDataFromStreamingKey from "./generateStreamDataFromStreamingKey"
const streamingServerAPIAddress = process.env.STREAMING_API_SERVER ?? ""

View File

@ -1,5 +1,7 @@
import { StreamingKey } from "@models"
const streamingServerAPIUri = process.env.STREAMING_API_SERVER ? `${process.env.STREAMING_API_SERVER.startsWith("https") ? "https" : "http"}://${process.env.STREAMING_API_SERVER.split("://")[1]}` : "Not available"
export default async (key) => {
// generate a stream from a streamkey
const streamingKey = await StreamingKey.findOne({
@ -12,9 +14,9 @@ export default async (key) => {
user_id: streamingKey.user_id,
username: streamingKey.username,
sources: {
rtmp: `${streamingIngestServer}/live/${streamingKey.username}`,
hls: `${streamingServerAPIAddress}/live/${streamingKey.username}/src.m3u8`,
flv: `${streamingServerAPIAddress}/live/${streamingKey.username}/src.flv`,
rtmp: `${process.env.STREAMING_INGEST_SERVER}/live/${streamingKey.username}`,
hls: `${streamingServerAPIUri}/live/${streamingKey.username}/src.m3u8`,
flv: `${streamingServerAPIUri}/live/${streamingKey.username}/src.flv`,
}
}

View File

@ -34,7 +34,7 @@ export default async (payload) => {
await info.save()
global.wsInterface.io.emit(`streaming.info_update.${payload.user_id}`, info)
global.websocket_instance.io.emit(`streaming.info_update.${payload.user_id}`, info)
return info
}

View File

@ -1,8 +1,8 @@
import getConnectedUsersFollowing from "../methods/getConnectedUsersFollowing"
import getConnectedUsersFollowing from "../services/getConnectedUsersFollowing"
export default {
method: "GET",
route: "/connected_following_users",
route: "/connected/followers",
middlewares: ["withAuthentication"],
fn: async (req, res) => {
const users = await getConnectedUsersFollowing({

View File

@ -1,39 +0,0 @@
import lodash from "lodash"
import { User } from "@models"
const AllowedAnonPublicGetters = [
"_id",
"username",
"fullName",
"avatar",
"roles"
]
export default {
method: "GET",
route: "/public_data",
middlewares: ["withOptionalAuthentication"],
fn: async (req, res) => {
let user = req.query?.username ?? req.user.username
if (!user) {
return res.status(400).json({
error: "No user provided",
})
}
user = await User.findOne({
username: user,
}).catch(() => null)
if (!user) {
return res.json({
user: null,
})
}
user = lodash.pick(user, AllowedAnonPublicGetters)
return res.json(user)
}
}

View File

@ -1,14 +1,35 @@
import lodash from "lodash"
import { User } from "@models"
const publicGetters = [
"_id",
"username",
"fullName",
"avatar",
"roles",
"badges",
"cover",
"verified",
"description",
]
export default {
method: "GET",
route: "/:user_id/data",
middlewares: ["withAuthentication"],
fn: async (req, res) => {
let user = await User.findOne(req.params.user_id)
let user = await User.findOne({
_id: req.params.user_id,
})
if (!user) {
return res.status(404).json({ error: "User not exists" })
}
if (req.user._id.toString() !== user._id.toString()) {
user = lodash.pick(user, publicGetters)
}
return res.json(user)
}
}

View File

@ -0,0 +1,19 @@
import { User } from "@models"
export default {
method: "GET",
route: "/user_id/:username",
middlewares: ["withAuthentication"],
fn: async (req, res) => {
const user = await User.findOne({ username: req.params.username })
if (!user) {
return res.status(404).json({ error: "User not exists" })
}
return res.json({
username: user.username,
user_id: user._id,
})
}
}

View File

@ -1,4 +1,4 @@
import UpdateUserData from "../methods/updateUserData"
import UpdateUserData from "../services/updateUserData"
export default {
method: "DELETE",
@ -7,7 +7,7 @@ export default {
fn: async (req, res) => {
const user_id = req.user._id.toString()
UpdateUserData.update({
UpdateUserData({
user_id: user_id,
update: {
fullName: undefined

View File

@ -1,6 +1,6 @@
import { Schematized } from "@lib"
import UpdateUserData from "../methods/updateUserData"
import { User } from "@models"
import UpdateUserData from "../services/updateUserData"
const AllowedPublicUpdateFields = [
"fullName",
@ -19,7 +19,7 @@ const MaxStringsLengths = {
export default {
method: "POST",
route: "/self/update_data",
middlewares: ["withAuthentication", "roles"],
middlewares: ["withAuthentication"],
fn: Schematized({
required: ["update"],
select: ["update"],
@ -43,7 +43,20 @@ export default {
}
})
UpdateUserData.update({
// check if email is already in use
if (typeof update.email !== "undefined") {
const user = await User.findOne({
email: update.email,
})
if (user) {
return res.status(400).json({
error: "Email is already in use",
})
}
}
UpdateUserData({
user_id: user_id,
update: update,
}).then((user) => {

View File

@ -1,7 +1,7 @@
import { Schematized } from "@lib"
import { User } from "@models"
import updateUserPassword from "../methods/updateUserPassword"
import updateUserPassword from "../services/updateUserPassword"
import bcrypt from "bcrypt"
export default {

View File

@ -0,0 +1,25 @@
import { UserFollow } from "@models"
export default async (payload = {}) => {
const { from_user_id } = payload
// get all the users that are following
const following = await UserFollow.find({
user_id: from_user_id,
})
// check if following users are connected
const connectedUsers = []
following.forEach((follow) => {
const connectedClient = global.websocket_instance.clients.find((client) => {
return client.user_id === follow.to
})
if (connectedClient) {
connectedUsers.push(connectedClient.user_id)
}
})
return connectedUsers
}

View File

@ -22,10 +22,10 @@ export default async (payload) => {
await user.save()
global.wsInterface.io.emit(`user.update`, {
global.websocket_instance.io.emit(`user.update`, {
...user.toObject(),
})
global.wsInterface.io.emit(`user.update.${payload.user_id}`, {
global.websocket_instance.io.emit(`user.update.${payload.user_id}`, {
...user.toObject(),
})

View File

@ -4,6 +4,7 @@ export { default as UserController } from "./UserController"
export { default as AuthController } from "./AuthController"
export { default as SessionController } from "./SessionController"
export { default as StatusController } from "./StatusController"
export { default as FollowController } from "./FollowController"
export { default as PostsController } from "./PostsController"

View File

@ -8,7 +8,7 @@ export default async (user_id) => {
// send event to ws clients (if are connected)
followers.forEach((follow) => {
const connectedClient = global.wsInterface.clients.find((client) => {
const connectedClient = global.websocket_instance.clients.find((client) => {
return client.user_id === follow.user_id
})

View File

@ -8,7 +8,7 @@ export default async (user_id) => {
// send event to ws clients (if are connected)
followers.forEach((follow) => {
const connectedClient = global.wsInterface.clients.find((client) => {
const connectedClient = global.websocket_instance.clients.find((client) => {
return client.user_id === follow.user_id
})

View File

@ -1,5 +1,5 @@
require("dotenv").config()
const crypto = require("node:crypto")
import { webcrypto as crypto } from "crypto"
// patches
const { Buffer } = require("buffer")

View File

@ -1,11 +0,0 @@
import { genV1 } from "../../essc"
export default (obj) => {
obj.essc = genV1({
type: obj.vaultItemTypeSelector ?? obj.type,
serial: obj.vaultItemSerial ?? obj.serial,
manufacturer: obj.vaultItemManufacturer ?? obj.manufacturer,
})
return obj
}

View File

@ -1,6 +0,0 @@
export default (obj) => {
return {
...obj,
...obj.properties,
}
}

View File

@ -1,17 +0,0 @@
export default (obj) => {
const fixedKeys = {
vaultItemManufacturer: "manufacturer",
vaultItemSerial: "serial",
vaultItemTypeSelector: "type",
vaultItemManufacturedYear: "manufacturedYear",
}
Object.keys(obj).forEach(key => {
if (fixedKeys[key]) {
obj[fixedKeys[key]] = obj[key]
delete obj[key]
}
})
return obj
}

View File

@ -1,22 +0,0 @@
export default async (additions, obj) => {
let query = []
if (Array.isArray(additions)) {
query = additions
}else {
query.push(additions)
}
for await(let addition of query) {
try {
let script = await import(`./handlers/${addition}.js`)
script = script.default || script
obj = await script(obj)
} catch (error) {
}
}
return obj
}

View File

@ -1,70 +0,0 @@
// random 5 digits number
const random5 = () => Math.floor(Math.random() * 90000) + 10000
// secure random 5 digits number
const random5Secure = () => {
const random = random5()
return random.toString().padStart(5, '0')
}
// aa-bbbbb-cccc
//* a: type (2 digits)
//* b: serial (5 digits)
//* c: manufacturer (4 digits)
const typesNumber = {
"computers-desktop": [1],
"computers-laptop": [2],
"computers-tablet": [3],
"computers-smartphone": [4],
"networking": [5],
"peripherals-printer": [6],
"peripherals-monitor": [7],
}
export function genV1(params) {
let { type, serial, manufacturer } = params // please in that order
type = type.toLowerCase()
let str = []
// Type parsing
let typeBuf = []
if (typeof typesNumber[type] === "undefined") {
typeBuf[0] = 0
typeBuf[1] = "X"
} else {
typeBuf[0] = typesNumber[type][0]
typeBuf[1] = typesNumber[type][1] ?? "X"
}
str.push(typeBuf.join(""))
// Serial parsing
// if serial is not defined, generate a random 4 digits number
if (typeof serial === "undefined") {
str.push(random5().toString())
} else {
// push last 5 digits of serial, if serial is not 5 digits, pad with 0
let serialBuf = []
serialBuf[0] = serial.slice(-5, -4) ?? "0"
serialBuf[1] = serial.slice(-4, -3) ?? "0"
serialBuf[2] = serial.slice(-3, -2) ?? "0"
serialBuf[3] = serial.slice(-2, -1) ?? "0"
serialBuf[4] = serial.slice(-1) ?? "0"
str.push(serialBuf.join(""))
}
// Manufacturer parsing
// abreviate manufacturer name to 4 letters
if (typeof manufacturer === "undefined") {
str.push("GENR")
} else {
str.push(manufacturer.slice(0, 4).toUpperCase())
}
return str.join("-")
}

View File

@ -1,4 +1,3 @@
export { default as Schematized } from "./schematized"
export { default as additionsHandler } from "./additionsHandler"
export * as Token from "./token"

View 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
}
}

View File

@ -1,5 +0,0 @@
export const errorHandler = (error, req, res, next) => {
res.json({ error: error.message })
}
export default errorHandler

View File

@ -1,30 +0,0 @@
export const hasPermissions = (req, res, next) => {
if (typeof (req.userData) == "undefined") {
return res.status(403).json(`User data is not available, please ensure if you are authenticated`)
}
const { _id, username, roles } = req.userData
const { permissions } = req.body
req.userPermissions = roles
let check = []
if (Array.isArray(permissions)) {
check = permissions
} else {
check.push(permissions)
}
if (check.length > 0) {
check.forEach((role) => {
if (!roles.includes(role)) {
return res.status(403).json(`${username} not have permissions ${permissions}`)
}
})
}
next()
}
export default hasPermissions

View File

@ -1,12 +1,6 @@
// const fileUpload = require("@nanoexpress/middleware-file-upload/cjs")()
export { default as withAuthentication } from "./withAuthentication"
export { default as withOptionalAuthentication } from "./withOptionalAuthentication"
export { default as errorHandler } from "./errorHandler"
export { default as hasPermissions } from "./hasPermissions"
export { default as roles } from "./roles"
export { default as onlyAdmin } from "./onlyAdmin"
export { default as permissions } from "./permissions"
// export { fileUpload as fileUpload }
export { default as permissions } from "./permissions"

View File

@ -1,83 +1,119 @@
import { Session, User } from "../../models"
import { Token } from "../../lib"
import { Session, User, authorizedServerTokens } from "@models"
import { Token } from "@lib"
import SecureEntry from "@lib/secureEntry"
import jwt from "jsonwebtoken"
export default (req, res, next) => {
export default async (req, res, next) => {
function reject(description) {
return res.status(403).json({ error: `${description ?? "Invalid session"}` })
}
const authHeader = req.headers?.authorization?.split(" ")
try {
const tokenAuthHeader = req.headers?.authorization?.split(" ")
const serverTokenHeader = req.headers?.server_token
if (authHeader && authHeader[0] === "Bearer") {
const token = authHeader[1]
let decoded = null
try {
decoded = jwt.decode(token)
} catch (error) {
console.error(error)
if (!serverTokenHeader && !tokenAuthHeader) {
return reject("Missing token header")
}
if (!decoded) {
return reject("Cannot decode token")
if (serverTokenHeader) {
const [client_id, token] = serverTokenHeader.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")
}
return next()
}
jwt.verify(token, global.jwtStrategy.secretOrKey, async (err) => {
const sessions = await Session.find({ user_id: decoded.user_id })
const currentSession = sessions.find((session) => session.token === token)
if (!serverTokenHeader && tokenAuthHeader && tokenAuthHeader[0] === "Bearer") {
const token = tokenAuthHeader[1]
let decoded = null
if (!currentSession) {
return reject("Cannot find session")
try {
decoded = jwt.decode(token)
} catch (error) {
console.error(error)
}
const userData = await User.findOne({ _id: currentSession.user_id }).select("+refreshToken")
if (!userData) {
return res.status(404).json({ error: "No user data found" })
if (!decoded) {
return reject("Cannot decode token")
}
// if cannot verify token, start regeneration process
if (err) {
// first check if token is only expired, if is corrupted, reject
if (err.name !== "TokenExpiredError") {
return reject("Invalid token, cannot regenerate")
jwt.verify(token, global.jwtStrategy.secretOrKey, async (err) => {
const sessions = await Session.find({ user_id: decoded.user_id })
const currentSession = sessions.find((session) => session.token === token)
if (!currentSession) {
return reject("Cannot find session")
}
let regenerationToken = null
const userData = await User.findOne({ _id: currentSession.user_id }).select("+refreshToken")
// check if this expired token has a regeneration token associated
const associatedRegenerationToken = await Token.getRegenerationToken(token)
if (!userData) {
return res.status(404).json({ error: "No user data found" })
}
if (associatedRegenerationToken) {
regenerationToken = associatedRegenerationToken.refreshToken
} else {
// create a new regeneration token with the expired token
regenerationToken = await Token.createNewRegenerationToken(token).catch((error) => {
// in case of error, reject
reject(error.message)
// if cannot verify token, start regeneration process
if (err) {
// first check if token is only expired, if is corrupted, reject
if (err.name !== "TokenExpiredError") {
return reject("Invalid token, cannot regenerate")
}
return null
let regenerationToken = null
// check if this expired token has a regeneration token associated
const associatedRegenerationToken = await Token.getRegenerationToken(token)
if (associatedRegenerationToken) {
regenerationToken = associatedRegenerationToken.refreshToken
} else {
// create a new regeneration token with the expired token
regenerationToken = await Token.createNewRegenerationToken(token).catch((error) => {
// in case of error, reject
reject(error.message)
return null
})
}
if (!regenerationToken) return
// now send the regeneration token to the client (start Expired Exception Event [EEE])
return res.status(401).json({
error: "Token expired",
refreshToken: regenerationToken.refreshToken,
})
}
if (!regenerationToken) return
req.user = userData
req.jwtToken = token
req.decodedToken = decoded
req.currentSession = currentSession
// now send the regeneration token to the client (start Expired Exception Event [EEE])
return res.status(401).json({
error: "Token expired",
refreshToken: regenerationToken.refreshToken,
})
}
req.user = userData
req.jwtToken = token
req.decodedToken = decoded
req.currentSession = currentSession
return next()
})
} else {
return reject("Missing token header")
return next()
})
}
} catch (error) {
console.error(error)
return res.status(500).json({ error: "This endpoint needs authentication, but an error occurred." })
}
}

View File

@ -0,0 +1,28 @@
export default {
name: "authorizedServerTokens",
collection: "authorizedServerTokens",
schema: {
client_id: {
type: String,
required: true,
},
token: {
type: String,
required: true,
},
access: {
type: Array,
default: [],
},
name: {
type: String,
},
description: {
type: String,
},
createdAt: {
type: Date,
default: Date.now,
},
}
}

View File

@ -0,0 +1,29 @@
import SecureEntry from "@lib/secureEntry"
import { authorizedServerTokens } from "@models"
const rootClientID = "00000000-0000-0000-000000000000"
export default async () => {
// check if process.env.SERVER_TOKEN is included in authorizedServerKeys
if (process.env.SERVER_TOKEN) {
console.log("Checking if server token is authorized on server tokens list...")
const secureEntries = new SecureEntry(authorizedServerTokens)
const currentServerToken = await secureEntries.get(rootClientID, undefined, {
keyName: "client_id",
})
// check if match or not exist, if not, update
if (currentServerToken !== process.env.SERVER_TOKEN) {
console.log("Server token is not authorized on server tokens list, updating...")
await secureEntries.set(rootClientID, process.env.SERVER_TOKEN, {
keyName: "client_id",
valueName: "token",
})
}
return true
}
}

View File

@ -1,5 +1,5 @@
import { User } from "../../models"
import createUser from "../../controllers/UserController/methods/createUser"
import { User } from "@models"
import createUser from "@controllers/UserController/services/createUser"
export default async () => {
// check if any user with includes admin role exists

View File

@ -1,6 +1,8 @@
import { default as dbAdmin } from "./dbAdmin"
import { default as authorizeSelfServerToken } from "./authorizeSelfServerToken"
// set here the setup functions
export default [
dbAdmin,
authorizeSelfServerToken,
]

View File

@ -1,4 +1,4 @@
import { User, Comment, SavedPost } from "../../models"
import { User, Comment, SavedPost } from "@models"
export default async (payload) => {
let {