Add admin login and password change endpoints
Introduces POST /login and PATCH /password routes for admin authentication and password management. Adds corresponding schema validation for login and password change, enforces stricter password requirements, and updates adminService with JWT-based profile retrieval and improved token handling.
This commit is contained in:
parent
d590ecce6d
commit
86349bcf4f
@ -1,4 +1,21 @@
|
||||
const express = require("express")
|
||||
const router = express.Router()
|
||||
const router = express.Router()
|
||||
const adminService = require("../../services/adminService")
|
||||
|
||||
router.post("/login", async (req, res) => {
|
||||
const { username, password } = req.body
|
||||
const result = await adminService.loginAdmin(username, password)
|
||||
return res.status(200).json(result)
|
||||
})
|
||||
|
||||
router.patch("/password", async (req, res) => {
|
||||
const token = req.headers.authorization.replace("Bearer ", "")
|
||||
const profile = await adminService.getAdminProfileByToken(token)
|
||||
|
||||
const { newPassword } = req.body
|
||||
|
||||
const result = await adminService.changeAdminPassword(profile.id, newPassword)
|
||||
return res.status(200).json(result)
|
||||
})
|
||||
|
||||
module.exports = router
|
||||
@ -2,6 +2,10 @@ const z = require("zod")
|
||||
|
||||
module.exports = {
|
||||
GET: {
|
||||
headers: z.object({
|
||||
"content-type": z.string().regex(/application\/json/i),
|
||||
"authorization": z.string().startsWith("Bearer ")
|
||||
}),
|
||||
query: z.object({
|
||||
uuid: z.string().uuid()
|
||||
})
|
||||
|
||||
@ -2,6 +2,10 @@ const z = require("zod")
|
||||
|
||||
module.exports = {
|
||||
GET: {
|
||||
headers: z.object({
|
||||
"content-type": z.string().regex(/application\/json/i),
|
||||
"authorization": z.string().startsWith("Bearer ")
|
||||
}),
|
||||
query: z.object({
|
||||
uuid: z.string().uuid()
|
||||
})
|
||||
|
||||
@ -6,9 +6,17 @@ const uuidSchema = z.object({
|
||||
|
||||
module.exports = {
|
||||
GET: {
|
||||
headers: z.object({
|
||||
"content-type": z.string().regex(/application\/json/i),
|
||||
"authorization": z.string().startsWith("Bearer ")
|
||||
}),
|
||||
query: uuidSchema
|
||||
},
|
||||
PUT: {
|
||||
headers: z.object({
|
||||
"content-type": z.string().regex(/application\/json/i),
|
||||
"authorization": z.string().startsWith("Bearer ")
|
||||
}),
|
||||
body: z.object({
|
||||
reasonKey: z.string().min(1),
|
||||
reasonMessage: z.string().optional(),
|
||||
@ -21,6 +29,10 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
DELETE: {
|
||||
headers: z.object({
|
||||
"content-type": z.string().regex(/application\/json/i),
|
||||
"authorization": z.string().startsWith("Bearer ")
|
||||
}),
|
||||
query: uuidSchema
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,10 @@ const z = require("zod")
|
||||
|
||||
module.exports = {
|
||||
DELETE: {
|
||||
headers: z.object({
|
||||
"content-type": z.string().regex(/application\/json/i),
|
||||
"authorization": z.string().startsWith("Bearer ")
|
||||
}),
|
||||
query: z.object({
|
||||
hash: z.string().length(64)
|
||||
})
|
||||
|
||||
17
schemas/admin/login.js
Normal file
17
schemas/admin/login.js
Normal file
@ -0,0 +1,17 @@
|
||||
const z = require("zod")
|
||||
|
||||
module.exports = {
|
||||
POST: {
|
||||
headers: {
|
||||
"content-type": z.string().regex(/application\/json/i)
|
||||
},
|
||||
body: {
|
||||
username: z.string()
|
||||
.min(1),
|
||||
password: z.string()
|
||||
.min(8, { message: "The password must be at least 8 characters long." })
|
||||
.regex(/[A-Z]/, { message: "The password must contain a capital letter." })
|
||||
.regex(/[0-9]/, { message: "The password must contain a number." })
|
||||
}
|
||||
}
|
||||
}
|
||||
16
schemas/admin/password.js
Normal file
16
schemas/admin/password.js
Normal file
@ -0,0 +1,16 @@
|
||||
const z = require("zod")
|
||||
|
||||
module.exports = {
|
||||
PATCH: {
|
||||
headers: z.object({
|
||||
"content-type": z.string().regex(/application\/json/i),
|
||||
"authorization": z.string().startsWith("Bearer ")
|
||||
}),
|
||||
body: z.object({
|
||||
newPassword: z.string()
|
||||
.min(8, { message: "The password must be at least 8 characters long." })
|
||||
.regex(/[A-Z]/, { message: "The password must contain a capital letter." })
|
||||
.regex(/[0-9]/, { message: "The password must contain a number." })
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,10 @@ const z = require("zod")
|
||||
|
||||
module.exports = {
|
||||
PATCH: {
|
||||
headers: z.object({
|
||||
"content-type": z.string().regex(/application\/json/i),
|
||||
"authorization": z.string().startsWith("Bearer ")
|
||||
}),
|
||||
body: z.object({
|
||||
newPassword: z.string()
|
||||
.min(8, { message: "The password must be at least 8 characters long." })
|
||||
|
||||
@ -2,12 +2,20 @@ const z = require("zod")
|
||||
|
||||
module.exports = {
|
||||
PUT: {
|
||||
headers: z.object({
|
||||
"content-type": z.string().regex(/application\/json/i),
|
||||
"authorization": z.string().startsWith("Bearer ")
|
||||
}),
|
||||
query: z.object({
|
||||
uuid: z.string().uuid(),
|
||||
hash: z.string().length(64)
|
||||
})
|
||||
},
|
||||
DELETE: {
|
||||
headers: z.object({
|
||||
"content-type": z.string().regex(/application\/json/i),
|
||||
"authorization": z.string().startsWith("Bearer ")
|
||||
}),
|
||||
query: z.object({
|
||||
uuid: z.string().uuid(),
|
||||
hash: z.string().length(64)
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
const jwt = require("jsonwebtoken")
|
||||
const bcrypt = require("bcryptjs")
|
||||
const userRepository = require("../repositories/userRepository")
|
||||
const adminRepository = require("../repositories/adminRepository")
|
||||
const bcrypt = require("bcryptjs")
|
||||
const { DefaultError } = require("../errors/errors")
|
||||
|
||||
const ADMIN_JWT_SECRET = process.env.ADMIN_JWT_SECRET || "udjJLGCOq7m3NmGpdVLJ@#"
|
||||
@ -28,7 +29,7 @@ async function checkAdminAccess(adminId, requiredPermission) {
|
||||
}
|
||||
|
||||
async function changeAdminPassword(adminId, newPlainPassword) {
|
||||
if (!newPlainPassword || newPlainPassword.length < 6) {
|
||||
if (!newPlainPassword || newPlainPassword.length < 8) {
|
||||
throw new DefaultError(400, "Le mot de passe doit contenir au moins 6 caractères.")
|
||||
}
|
||||
|
||||
@ -52,6 +53,15 @@ async function getAdminProfile(adminId) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getAdminProfileByToken(accessToken) {
|
||||
try {
|
||||
const decoded = jwt.verify(accessToken, { complete: true, json: true })
|
||||
return getAdminProfile(decoded.sub)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async function grantPermission(adminId, permissionKey) {
|
||||
return await adminRepository.assignPermission(adminId, permissionKey)
|
||||
}
|
||||
@ -74,7 +84,7 @@ async function loginAdmin(username, password) {
|
||||
const token = jwt.sign(
|
||||
{ id: admin.id, username: admin.username, type: "admin" },
|
||||
ADMIN_JWT_SECRET,
|
||||
{ expiresIn: "8h" }
|
||||
{ expiresIn: "8h", subject: admin.id, issuer: "Yggdrasil" }
|
||||
)
|
||||
|
||||
return { token }
|
||||
@ -147,5 +157,6 @@ module.exports = {
|
||||
logPlayerAction,
|
||||
revokePermission,
|
||||
checkAdminAccess,
|
||||
changeAdminPassword
|
||||
changeAdminPassword,
|
||||
getAdminProfileByToken
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user