Add bans, user cache, auth routes & token changes

Introduce user bans and caching, extend schemas, and add auth/user endpoints. Adds bans DDL and banRepo plus adminService to manage bans; introduces modules/cache for in-memory user caching used by authService and userService. Updates users and credentials DDLs (role, isDisabled, shouldReset). Changes tokensService to include role in access tokens and store refresh tokens as SHA-256 hashes. Updates repos (credentials create RETURNING, usersRepo.updateProfile positional params), enhances authService with isLogged/isNotLogged middleware, ban checks, and refreshed token flow. Adds auth routes (login, logout, refresh, register) and user routes (/me).
This commit is contained in:
2026-09-16 01:45:44 +02:00
parent 22542243f6
commit 38882d7b87
17 changed files with 272 additions and 20 deletions
+38
View File
@@ -0,0 +1,38 @@
const { DefaultError } = require("../errors/errors")
const banRepo = require("../repositories/banRepo")
async function banUser(userId, message, expiresAt) {
const existingBan = await banRepo.isBanned(userId)
if (existingBan) {
throw new DefaultError(409, "Already has an active ban.", "Duplicate active ban.")
}
return await banRepo.banUser(userId, message, expiresAt)
}
async function unbanUser(banId) {
const existingBan = await banRepo.isBanned(userId)
if (!existingBan) {
throw new DefaultError(404, "No active ban.", "No active ban found.")
}
return await banRepo.unbanUser(banId)
}
async function isUserBanned(userId) {
const ban = await banRepo.isBanned(userId)
if (!ban) {
return false
}
if (new Date(ban.expiresAt) <= new Date()) {
await banRepo.deactivate(ban.id)
return false
}
return true
}
module.exports = {
banUser,
unbanUser,
isUserBanned
}
+45 -6
View File
@@ -1,5 +1,7 @@
const cache = require("../modules/cache")
const bcrypt = require("bcryptjs")
const usersRepo = require("../repositories/usersRepo")
const adminService = require("./adminService")
const tokensService = require("./tokensService")
const identitiesRepo = require("../repositories/identitiesRepo")
const credentialsRepo = require("../repositories/credentialsRepo")
@@ -10,7 +12,7 @@ async function registerLocal({ identifier, password, profile = { displayName, av
const user = await usersRepo.create(identifier, process.env.SERVER_UUID, null, profile.displayName, profile.avatarURL)
const hashedPassword = await bcrypt.hash(password, Number(process.env.BCRYPT_SALT))
const credentials = await credentialsRepo.create(user.id, hashedPassword)
if (Array.isArray(credentials)) {
if (credentials.length == 0) {
throw new DefaultError(500, "Internal Server Error", "UserRegistration")
}
return user
@@ -41,13 +43,13 @@ async function loginLocal({ identifier, password }) {
}
}
async function refreshAccessToken({ userId, refreshToken }) {
async function refreshAccessToken({ refreshToken }) {
try {
await tokensService.verifyRefreshToken(refreshToken)
const payload = await tokensService.verifyRefreshToken(refreshToken)
await tokensService.revokeRefreshToken(refreshToken)
const user = await usersRepo.findById(userId)
const user = await usersRepo.findById(payload.userId)
const newAccessToken = await tokensService.signToken(user)
const newRefreshToken = await tokensService.signRefreshToken(userId)
const newRefreshToken = await tokensService.signRefreshToken(user.id)
return { accessToken: newAccessToken, refreshToken: newRefreshToken }
} catch (error) {
throw error
@@ -74,10 +76,47 @@ async function resetPassword({ userId, newPassword }) {
}
}
async function isNotLogged(req, res, next) {
if (req.headers.authorization && req.headers.authorization.startsWith("Bearer")) {
throw new DefaultError(401, "You must be logged out to do this action", "Auth header")
}
next()
}
async function isLogged(req, res, next) {
if (!req.headers.authorization) {
throw new DefaultError(401, "You must be logged in to do this action", "Auth header")
}
if (!req.headers.authorization.startsWith("Bearer ")) {
throw new DefaultError(422, "Wrong auth header format", "WhereIsBearer")
}
const tokenVerified = await tokensService.verifyAccessToken(req.headers.authorization.replace("Bearer ", ""))
const isBanned = await adminService.isUserBanned(tokenVerified.id)
if (isBanned) {
throw new DefaultError(403, "You're not allowed to do this action", "Banned")
}
let user = cache.users.get(tokenVerified.id)
if (!user) {
user = await usersRepo.findById(tokenVerified.id)
if (!user) {
throw new DefaultError(404, "User not found", "UserNotFound")
}
cache.users.put(tokenVerified.id, user)
}
req.user = user
next()
}
module.exports = {
isLogged,
loginLocal,
isNotLogged,
resetPassword,
registerLocal,
changePassword,
refreshAccessToken
refreshAccessToken,
}
+8 -5
View File
@@ -1,4 +1,5 @@
const jwt = require("jsonwebtoken")
const crypto = require("node:crypto")
const security = require("../modules/security")
const tokenRepo = require("../repositories/refreshTokensRepo")
const { DefaultError } = require("../errors/errors")
@@ -12,7 +13,8 @@ async function signToken(user) {
id: user.id,
identifier: user.identifier,
displayName: user.displayName,
avatarUrl: user.avatarUrl
avatarUrl: user.avatarUrl,
role: user.role
}
const token = await jwt.sign(payload, privateKey, {
algorithm: "RS256",
@@ -31,7 +33,6 @@ async function verifyAccessToken(token) {
algorithms: ["RS256"],
issuer: process.env.FQDN
})
console.log(result)
return result
} catch (error) {
throw error
@@ -56,7 +57,8 @@ async function signRefreshToken(userId) {
expiresIn: expiresIn
}
)
await tokenRepo.create(userId, token, expiresAtInMs)
const hash = crypto.createHash("sha256").update(token).digest("hex")
await tokenRepo.create(userId, hash, expiresAtInMs)
return token
} catch (error) {
throw error
@@ -80,11 +82,12 @@ async function verifyRefreshToken(token) {
if (!result) {
throw new DefaultError(500, "Internal Server Error")
}
const refreshTokenInDb = await tokenRepo.findByTokenHash(token)
const hash = crypto.createHash("sha256").update(token).digest("hex")
const refreshTokenInDb = await tokenRepo.findByTokenHash(hash)
if (refreshTokenInDb == null) {
throw new DefaultError(403, "Bad refresh token", "Refresh token doesn't exists")
}
return true
return result
} catch (error) {
throw error
}
+6
View File
@@ -1,10 +1,16 @@
const userRepo = require("../repositories/usersRepo")
const cache = require("../modules/cache")
async function getProfile({ userId }) {
const cachedUser = cache.users.get(userId)
if (cachedUser) {
return cachedUser
}
return await userRepo.findById(userId)
}
async function updateProfile({ userId, profile = { displayName, avatarURL } }) {
cache.users.delete(userId)
return await userRepo.updateProfile(userId, profile.displayName, profile.avatarURL)
}