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 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
|
module.exports = router
|
||||||
@ -2,6 +2,10 @@ const z = require("zod")
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
GET: {
|
GET: {
|
||||||
|
headers: z.object({
|
||||||
|
"content-type": z.string().regex(/application\/json/i),
|
||||||
|
"authorization": z.string().startsWith("Bearer ")
|
||||||
|
}),
|
||||||
query: z.object({
|
query: z.object({
|
||||||
uuid: z.string().uuid()
|
uuid: z.string().uuid()
|
||||||
})
|
})
|
||||||
|
|||||||
@ -2,6 +2,10 @@ const z = require("zod")
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
GET: {
|
GET: {
|
||||||
|
headers: z.object({
|
||||||
|
"content-type": z.string().regex(/application\/json/i),
|
||||||
|
"authorization": z.string().startsWith("Bearer ")
|
||||||
|
}),
|
||||||
query: z.object({
|
query: z.object({
|
||||||
uuid: z.string().uuid()
|
uuid: z.string().uuid()
|
||||||
})
|
})
|
||||||
|
|||||||
@ -6,9 +6,17 @@ const uuidSchema = z.object({
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
GET: {
|
GET: {
|
||||||
|
headers: z.object({
|
||||||
|
"content-type": z.string().regex(/application\/json/i),
|
||||||
|
"authorization": z.string().startsWith("Bearer ")
|
||||||
|
}),
|
||||||
query: uuidSchema
|
query: uuidSchema
|
||||||
},
|
},
|
||||||
PUT: {
|
PUT: {
|
||||||
|
headers: z.object({
|
||||||
|
"content-type": z.string().regex(/application\/json/i),
|
||||||
|
"authorization": z.string().startsWith("Bearer ")
|
||||||
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
reasonKey: z.string().min(1),
|
reasonKey: z.string().min(1),
|
||||||
reasonMessage: z.string().optional(),
|
reasonMessage: z.string().optional(),
|
||||||
@ -21,6 +29,10 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
DELETE: {
|
DELETE: {
|
||||||
|
headers: z.object({
|
||||||
|
"content-type": z.string().regex(/application\/json/i),
|
||||||
|
"authorization": z.string().startsWith("Bearer ")
|
||||||
|
}),
|
||||||
query: uuidSchema
|
query: uuidSchema
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2,6 +2,10 @@ const z = require("zod")
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
DELETE: {
|
DELETE: {
|
||||||
|
headers: z.object({
|
||||||
|
"content-type": z.string().regex(/application\/json/i),
|
||||||
|
"authorization": z.string().startsWith("Bearer ")
|
||||||
|
}),
|
||||||
query: z.object({
|
query: z.object({
|
||||||
hash: z.string().length(64)
|
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 = {
|
module.exports = {
|
||||||
PATCH: {
|
PATCH: {
|
||||||
|
headers: z.object({
|
||||||
|
"content-type": z.string().regex(/application\/json/i),
|
||||||
|
"authorization": z.string().startsWith("Bearer ")
|
||||||
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
newPassword: z.string()
|
newPassword: z.string()
|
||||||
.min(8, { message: "The password must be at least 8 characters long." })
|
.min(8, { message: "The password must be at least 8 characters long." })
|
||||||
|
|||||||
@ -2,12 +2,20 @@ const z = require("zod")
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
PUT: {
|
PUT: {
|
||||||
|
headers: z.object({
|
||||||
|
"content-type": z.string().regex(/application\/json/i),
|
||||||
|
"authorization": z.string().startsWith("Bearer ")
|
||||||
|
}),
|
||||||
query: z.object({
|
query: z.object({
|
||||||
uuid: z.string().uuid(),
|
uuid: z.string().uuid(),
|
||||||
hash: z.string().length(64)
|
hash: z.string().length(64)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
DELETE: {
|
DELETE: {
|
||||||
|
headers: z.object({
|
||||||
|
"content-type": z.string().regex(/application\/json/i),
|
||||||
|
"authorization": z.string().startsWith("Bearer ")
|
||||||
|
}),
|
||||||
query: z.object({
|
query: z.object({
|
||||||
uuid: z.string().uuid(),
|
uuid: z.string().uuid(),
|
||||||
hash: z.string().length(64)
|
hash: z.string().length(64)
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
|
const jwt = require("jsonwebtoken")
|
||||||
|
const bcrypt = require("bcryptjs")
|
||||||
const userRepository = require("../repositories/userRepository")
|
const userRepository = require("../repositories/userRepository")
|
||||||
const adminRepository = require("../repositories/adminRepository")
|
const adminRepository = require("../repositories/adminRepository")
|
||||||
const bcrypt = require("bcryptjs")
|
|
||||||
const { DefaultError } = require("../errors/errors")
|
const { DefaultError } = require("../errors/errors")
|
||||||
|
|
||||||
const ADMIN_JWT_SECRET = process.env.ADMIN_JWT_SECRET || "udjJLGCOq7m3NmGpdVLJ@#"
|
const ADMIN_JWT_SECRET = process.env.ADMIN_JWT_SECRET || "udjJLGCOq7m3NmGpdVLJ@#"
|
||||||
@ -28,7 +29,7 @@ async function checkAdminAccess(adminId, requiredPermission) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function changeAdminPassword(adminId, newPlainPassword) {
|
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.")
|
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) {
|
async function grantPermission(adminId, permissionKey) {
|
||||||
return await adminRepository.assignPermission(adminId, permissionKey)
|
return await adminRepository.assignPermission(adminId, permissionKey)
|
||||||
}
|
}
|
||||||
@ -74,7 +84,7 @@ async function loginAdmin(username, password) {
|
|||||||
const token = jwt.sign(
|
const token = jwt.sign(
|
||||||
{ id: admin.id, username: admin.username, type: "admin" },
|
{ id: admin.id, username: admin.username, type: "admin" },
|
||||||
ADMIN_JWT_SECRET,
|
ADMIN_JWT_SECRET,
|
||||||
{ expiresIn: "8h" }
|
{ expiresIn: "8h", subject: admin.id, issuer: "Yggdrasil" }
|
||||||
)
|
)
|
||||||
|
|
||||||
return { token }
|
return { token }
|
||||||
@ -147,5 +157,6 @@ module.exports = {
|
|||||||
logPlayerAction,
|
logPlayerAction,
|
||||||
revokePermission,
|
revokePermission,
|
||||||
checkAdminAccess,
|
checkAdminAccess,
|
||||||
changeAdminPassword
|
changeAdminPassword,
|
||||||
|
getAdminProfileByToken
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user