File size: 2,816 Bytes
e8c33fa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
const jwt = require('jsonwebtoken');
const AdminUser = require('../models/AdminUser');
const { env } = require('../configs/env');

const JWT_SECRET = env.jwtSecret;

const readTokenFromRequest = (req) => {
  const header = req.headers?.authorization || req.headers?.Authorization;
  if (typeof header === 'string') {
    const [scheme, value] = header.split(' ');
    if (scheme && scheme.toLowerCase() === 'bearer' && value) {
      return value.trim();
    }
  }

  return req.cookies?.token || '';
};

const protect = async (req, res, next) => {
  if (!JWT_SECRET) {
    return res.status(503).json({ message: "Authentication is not configured" });
  }

  const token = readTokenFromRequest(req);

  if (!token) {
    return res.status(401).json({ message: "Not authorized, no token" });
  }

  try {
    const decoded = jwt.verify(token, JWT_SECRET);
    const adminId = decoded?.id || decoded?.sub;
    if (!adminId) {
      return res.status(401).json({ message: "Not authorized, invalid token" });
    }

    const admin = await AdminUser.findById(adminId).select('-passwordHash +sessionToken').lean();
    if (!admin) {
      return res.status(401).json({ message: "User not found" });
    }

    if (admin.isActive === false) {
      return res.status(403).json({ message: "Account is inactive" });
    }

    if (decoded.st && admin.sessionToken && decoded.st !== admin.sessionToken) {
      return res.status(401).json({ message: "Session invalidated", code: "SESSION_CONFLICT" });
    }

    req.user = {
      ...admin,
      _id: admin._id,
      id: String(admin._id),
      role: admin.role || "admin",
    };
    // Belt-and-suspenders: projection already excludes this field; explicit scrub guards against future query changes.
    delete req.user.passwordHash;

    return next();
  } catch (error) {
    return res.status(401).json({ message: "Not authorized, invalid token" });
  }
};

const adminOnly = (req, res, next) => {
  if (req.user && req.user.role === "admin") {
    next();
  } else {
    res.status(403).json({ message: "Access denied: Admins only" });
  }
};

const optionalProtect = async (req, res, next) => {
  if (!JWT_SECRET) return next();
  const token = readTokenFromRequest(req);
  if (!token) return next();
  try {
    const decoded = jwt.verify(token, JWT_SECRET);
    const adminId = decoded?.id || decoded?.sub;
    if (adminId) {
      const admin = await AdminUser.findById(adminId).select('-passwordHash').lean();
      if (admin && admin.isActive !== false) {
        req.user = {
          ...admin,
          _id: admin._id,
          id: String(admin._id),
          role: admin.role || 'admin',
        };
      }
    }
  } catch {
    // Invalid token — proceed as unauthenticated
  }
  next();
};

module.exports = { protect, adminOnly, optionalProtect };