generated from azures04/Base-REST-API
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:
@@ -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
@@ -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,
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user