File size: 4,010 Bytes
eeb3436
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
const { AppError } = require("../utils/appError");

function extractBearerToken(authorizationHeader) {
  if (!authorizationHeader || typeof authorizationHeader !== "string") {
    return null;
  }

  const [scheme, token] = authorizationHeader.trim().split(/\s+/, 2);
  if (!scheme || !token) {
    return null;
  }

  if (scheme.toLowerCase() !== "bearer") {
    return null;
  }

  return token;
}

function createRequireAuthMiddleware(authService) {
  if (!authService) {
    throw new Error("createRequireAuthMiddleware requires authService.");
  }

  return async function requireAuth(req, _res, next) {
    try {
      const token = extractBearerToken(req.headers.authorization);
      if (!token) {
        throw new AppError("Authorization bearer token is required.", 401, {
          code: "TOKEN_MISSING",
        });
      }

      const decoded = authService.verifyAccessToken(token);
      const userId = String(decoded.sub || "").trim();
      if (!userId) {
        throw new AppError("Invalid token subject.", 401, {
          code: "TOKEN_SUBJECT_INVALID",
        });
      }

      const authenticatedUser = await authService.getAuthenticatedUserById(userId);

      req.auth = {
        id: authenticatedUser.id,
        email: authenticatedUser.email,
        fullName: authenticatedUser.fullName,
        roles: authenticatedUser.roles,
        isSuperAdmin: authenticatedUser.isSuperAdmin,
        tenant: authenticatedUser.tenant,
      };

      if (!req.context) {
        req.context = {};
      }
      req.context.actorUserId = authenticatedUser.id;

      return next();
    } catch (error) {
      return next(error);
    }
  };
}

function createRequireRolesMiddleware(allowedRoles) {
  const normalizedAllowed = (Array.isArray(allowedRoles) ? allowedRoles : [])
    .filter((role) => typeof role === "string")
    .map((role) => role.trim().toLowerCase())
    .filter(Boolean);

  return function requireRoles(req, _res, next) {
    if (!req.auth) {
      return next(new AppError("Unauthorized.", 401));
    }

    if (req.auth.isSuperAdmin) {
      return next();
    }

    if (normalizedAllowed.length === 0) {
      return next();
    }

    const roleSet = new Set((req.auth.roles || []).map((role) => String(role).toLowerCase()));
    const hasAllowedRole = normalizedAllowed.some((role) => roleSet.has(role));

    if (!hasAllowedRole) {
      return next(
        new AppError("Insufficient permissions for this action.", 403, {
          code: "RBAC_FORBIDDEN",
          requiredRoles: normalizedAllowed,
        })
      );
    }

    return next();
  };
}

function createTenantScopeMiddleware() {
  return function enforceTenantScope(req, _res, next) {
    if (!req.auth) {
      return next();
    }

    if (!req.context) {
      req.context = {};
    }

    const requestedTenantCodeRaw = req.query?.tenantCode;
    const requestedTenantCode =
      typeof requestedTenantCodeRaw === "string" && requestedTenantCodeRaw.trim()
        ? requestedTenantCodeRaw.trim()
        : null;

    if (req.auth.isSuperAdmin) {
      req.context.tenantCode = requestedTenantCode;
      if (requestedTenantCode) {
        req.query.tenantCode = requestedTenantCode;
      }
      return next();
    }

    const userTenantCode = req.auth.tenant?.code || null;
    if (!userTenantCode) {
      return next(
        new AppError("Authenticated user has no tenant scope.", 403, {
          code: "TENANT_SCOPE_MISSING",
        })
      );
    }

    if (requestedTenantCode && requestedTenantCode !== userTenantCode) {
      return next(
        new AppError("Cross-tenant access is not allowed.", 403, {
          code: "TENANT_SCOPE_VIOLATION",
          requestedTenantCode,
          tenantCode: userTenantCode,
        })
      );
    }

    req.context.tenantCode = userTenantCode;
    req.query.tenantCode = userTenantCode;

    return next();
  };
}

module.exports = {
  createRequireAuthMiddleware,
  createRequireRolesMiddleware,
  createTenantScopeMiddleware,
};