[fix] Use db permissions instead of jwt
All checks were successful
Test - Build & Deploy to Scaleway / build-and-push-images-lecoffre (push) Successful in 1m44s
Test - Build & Deploy to Scaleway / deploy-back-lecoffre (push) Successful in 3s
Test - Build & Deploy to Scaleway / deploy-cron-lecoffre (push) Successful in 2s

This commit is contained in:
Sosthene 2025-08-03 19:02:34 +02:00
parent 25b9388f29
commit 55b9d2e8a6
5 changed files with 145 additions and 76 deletions

View File

@ -5,10 +5,9 @@ import { Service } from "typedi";
import AuthService, { IUserJwtPayload, PROVIDER_OPENID } from "@Services/common/AuthService/AuthService";
import IdNotService from "@Services/common/IdNotService/IdNotService";
import User, { RulesGroup } from "le-coffre-resources/dist/Admin";
import User from "le-coffre-resources/dist/Admin";
import UsersService from "@Services/super-admin/UsersService/UsersService";
import SubscriptionsService from "@Services/admin/SubscriptionsService/SubscriptionsService.ts";
import RulesGroupsService from "@Services/admin/RulesGroupsService/RulesGroupsService";
@Controller()
@Service()
@ -18,7 +17,6 @@ export default class UserController extends ApiController {
private idNotService: IdNotService,
private userService: UsersService,
private subscriptionsService: SubscriptionsService,
private rulesGroupsService: RulesGroupsService,
) {
super();
}
@ -75,47 +73,18 @@ export default class UserController extends ApiController {
this.httpUnauthorized(response, "Email not found");
return;
}
let isSubscribed = await this.subscriptionsService.isUserSubscribed(user.uid, userHydrated.office_membership?.uid!);
//Check if user is whitelisted
// const isWhitelisted = await this.whitelistService.getByEmail(userHydrated.contact!.email);
//When we'll switch to idNotId whitelisting
// const isWhitelisted = await this.userWhitelistService.getByIdNotId(user.idNot);
//If not whitelisted, return 409 Not whitelisted
// if (!isWhitelisted || isWhitelisted.length === 0) {
// this.httpNotWhitelisted(response);
// return;
// }
await this.idNotService.updateOffice(user.office_uid);
const payload = await this.authService.getUserJwtPayload(user.idNot);
console.log("payload", payload);
if (!payload) {
console.error("No payload");
return;
}
if (!isSubscribed && (userHydrated.role?.name === "admin" || userHydrated.role?.name === "super-admin")) {
const manageSubscriptionRulesEntity = await this.rulesGroupsService.get({
where: { uid: "94343601-04c8-44ef-afb9-3047597528a9" },
include: { rules: true },
});
const manageSubscriptionRules = RulesGroup.hydrateArray<RulesGroup>(manageSubscriptionRulesEntity, {
strategy: "excludeAll",
});
if (!manageSubscriptionRules[0]) return;
payload.rules = manageSubscriptionRules[0].rules!.map((rule) => rule.name) || [];
isSubscribed = true;
}
// Check subscription status - but don't modify JWT rules since RulesHandler will fetch from DB
let isSubscribed = await this.subscriptionsService.isUserSubscribed(user.uid, userHydrated.office_membership?.uid!);
if (!isSubscribed) {
console.error("User not subscribed");
@ -162,20 +131,11 @@ export default class UserController extends ApiController {
)) as IUserJwtPayload;
let isSubscribed = await this.subscriptionsService.isUserSubscribed(newUserPayload.userId, newUserPayload.office_Id);
if (!isSubscribed && (newUserPayload.role === "admin" || newUserPayload.role === "super-admin")) {
const manageSubscriptionRulesEntity = await this.rulesGroupsService.get({
where: { uid: "94343601-04c8-44ef-afb9-3047597528a9" },
include: { rules: true },
});
const manageSubscriptionRules = RulesGroup.hydrateArray<RulesGroup>(manageSubscriptionRulesEntity, {
strategy: "excludeAll",
});
if (!manageSubscriptionRules[0]) return;
newUserPayload.rules = manageSubscriptionRules[0].rules!.map((rule) => rule.name) || [];
isSubscribed = true;
// Check subscription status - but don't modify JWT rules since RulesHandler will fetch from DB
if (!isSubscribed) {
console.error("User not subscribed during token refresh");
this.httpUnauthorized(response, "User not subscribed");
return;
}
delete newUserPayload.iat;
delete newUserPayload.exp;

View File

@ -1,34 +1,92 @@
import HttpCodes from "@Common/system/controller-pattern/HttpCodes";
import { NextFunction, Request, Response } from "express";
import Container from "typedi";
import UsersService from "@Services/super-admin/UsersService/UsersService";
import subscriptionRulesHandler from "./SubscriptionRulesHandler";
export default async function ruleHandler(req: Request, response: Response, next: NextFunction) {
try {
const rules = req.body.user.rules;
const userId = req.body.user.userId;
const service = req.path && req.path.split("/")[4];
const requiredRule = req.method + " " + service;
if (!rules) {
response.status(HttpCodes.UNAUTHORIZED).send("Unauthorized without rules");
if (!userId) {
response.status(HttpCodes.UNAUTHORIZED).send("Unauthorized - no user ID");
return;
}
// Fetch user rules directly from database instead of trusting JWT
const usersService = Container.get(UsersService);
const user = await usersService.getByUid(userId, {
role: { include: { rules: true } },
office_role: { include: { rules: true } }
}) as any; // Type assertion for database result with includes
if (!user) {
console.error(`User not found in database: ${userId}`);
response.status(HttpCodes.UNAUTHORIZED).send("Unauthorized - user not found");
return;
}
// Build rules array from database
const rules: string[] = [];
// Add role rules
if (user.role && user.role.rules) {
user.role.rules.forEach((rule: any) => {
rules.push(rule.name);
});
}
// Add office role rules (if any)
if (user.office_role && user.office_role.rules) {
user.office_role.rules.forEach((rule: any) => {
if (!rules.includes(rule.name)) {
rules.push(rule.name);
}
});
}
// Check if user has required rule
if (!rules.includes(requiredRule)) {
console.error(`Rule check failed for user ${req.body.user.userId}:`);
console.error(`Rule check failed for user ${userId}:`);
console.error(` Required rule: "${requiredRule}"`);
console.error(` User rules: [${rules.join(", ")}]`);
console.error(` User role: ${user.role?.name}`);
console.error(` User office role: ${user.office_role?.name}`);
console.error(` Database rules: [${rules.join(", ")}]`);
console.error(` Path: ${req.path}`);
console.error(` Method: ${req.method}`);
console.error(` Service: ${service}`);
response.status(HttpCodes.UNAUTHORIZED).send(
`Unauthorized with those rules. Required: "${requiredRule}", Provided: [${rules.join(", ")}]`
`Unauthorized. Required: "${requiredRule}", User has: [${rules.join(", ")}]`
);
return;
}
// Update the request with the fresh rules from database
req.body.user.rules = rules;
console.log(`[RulesHandler] User ${userId} authorized for ${requiredRule}`);
// Apply subscription rules if needed (for admin/super-admin users)
await subscriptionRulesHandler(req, response, () => {
// Check if user has required rule after subscription rules are applied
if (!req.body.user.rules.includes(requiredRule)) {
console.error(`Rule check failed for user ${userId} after subscription rules:`);
console.error(` Required rule: "${requiredRule}"`);
console.error(` Final rules: [${req.body.user.rules.join(", ")}]`);
response.status(HttpCodes.UNAUTHORIZED).send(
`Unauthorized. Required: "${requiredRule}", User has: [${req.body.user.rules.join(", ")}]`
);
return;
}
next();
});
} catch (error) {
console.error(error);
console.error(`[RulesHandler] Error checking rules for user:`, error);
response.status(HttpCodes.INTERNAL_ERROR).send("Internal server error");
return;
}

View File

@ -0,0 +1,63 @@
import { NextFunction, Request, Response } from "express";
import Container from "typedi";
import SubscriptionsService from "@Services/admin/SubscriptionsService/SubscriptionsService.ts";
import RulesGroupsService from "@Services/admin/RulesGroupsService/RulesGroupsService";
import { RulesGroup } from "le-coffre-resources/dist/Admin";
/**
* Middleware to add subscription management rules for admin/super-admin users
* who aren't subscribed but need to manage subscriptions
*/
export default async function subscriptionRulesHandler(req: Request, response: Response, next: NextFunction) {
try {
const userId = req.body.user.userId;
const userRole = req.body.user.role;
const officeId = req.body.user.office_Id;
// Only apply to admin/super-admin users
if (userRole !== "admin" && userRole !== "super-admin") {
return next();
}
// Check if user is subscribed
const subscriptionsService = Container.get(SubscriptionsService);
const isSubscribed = await subscriptionsService.isUserSubscribed(userId, officeId);
// If subscribed, no need to add subscription management rules
if (isSubscribed) {
return next();
}
// User is admin/super-admin but not subscribed - add subscription management rules
console.log(`[SubscriptionRulesHandler] Admin user ${userId} not subscribed, adding subscription management rules`);
const rulesGroupsService = Container.get(RulesGroupsService);
const manageSubscriptionRulesEntity = await rulesGroupsService.get({
where: { uid: "94343601-04c8-44ef-afb9-3047597528a9" },
include: { rules: true },
});
const manageSubscriptionRules = RulesGroup.hydrateArray<RulesGroup>(manageSubscriptionRulesEntity, {
strategy: "excludeAll",
});
if (!manageSubscriptionRules[0]) {
console.error("[SubscriptionRulesHandler] Subscription rules group not found");
return next();
}
// Add subscription rules to existing rules
const subscriptionRules = manageSubscriptionRules[0].rules!.map((rule) => rule.name) || [];
console.log(`[SubscriptionRulesHandler] Adding subscription rules:`, subscriptionRules);
console.log(`[SubscriptionRulesHandler] Original rules:`, req.body.user.rules);
req.body.user.rules = [...new Set([...req.body.user.rules, ...subscriptionRules])];
console.log(`[SubscriptionRulesHandler] Final rules after adding subscription rules:`, req.body.user.rules);
next();
} catch (error) {
console.error(`[SubscriptionRulesHandler] Error adding subscription rules:`, error);
// Don't fail the request, just continue without subscription rules
next();
}
}

View File

@ -814,14 +814,14 @@ export default async function main() {
label: "Notaire",
created_at: new Date(),
updated_at: new Date(),
rules: [],
rules: globalRules, // Fixed: was empty array
},
{
name: "default",
label: "Utilisateur",
created_at: new Date(),
updated_at: new Date(),
rules: [],
rules: globalRules, // Fixed: was empty array
},
];
@ -858,7 +858,7 @@ export default async function main() {
contact: contacts[2],
office_membership: offices[0],
role: roles[2],
office_role: officeRoles[2],
office_role: officeRoles[0], // Fixed: was officeRoles[2]
},
{
created_at: new Date(),
@ -867,7 +867,7 @@ export default async function main() {
contact: contacts[3],
office_membership: offices[0],
role: roles[1],
office_role: officeRoles[3],
office_role: officeRoles[1], // Fixed: was officeRoles[3]
},
{
created_at: new Date(),

View File

@ -33,7 +33,7 @@ export interface IUserJwtPayload {
};
office_Id: string;
role: string;
rules: string[];
rules: string[]; // Kept for backward compatibility, but will be empty in JWT
iat?: number;
exp?: number;
}
@ -62,26 +62,14 @@ export default class AuthService extends BaseService {
if (!user) return null;
const rules: string[] = [];
user.role.rules.forEach((rule) => {
rules.push(rule.name);
});
if (user.office_role) {
user.office_role.rules.forEach((rule) => {
if (!rules.includes(rule.name)) {
rules.push(rule.name);
}
});
}
// Since RulesHandler now fetches rules from database, we can simplify JWT payload
// Rules are no longer included in JWT for security reasons
return {
userId: user.uid,
openId: { providerName: providerName, userId: user.idNot },
office_Id: user.office_membership.uid,
role: user.role.name,
rules: rules,
rules: [], // Rules will be fetched fresh from database by RulesHandler
};
}
public generateAccessToken(user: any): string {