R145-Hygiene: limit-Floor + getContracts fail-closed (Konsistenz)
Pentester R145 (nice-to-have, keine Findings):
1) listAll: limit bekommt einen Floor (Math.max(1, ...)) - limit=-5
ergab vorher take:-5 an Prisma. page ebenso auf >=1 geklemmt.
2) getContracts nutzte dasselbe 'if (isCustomerPortal && customerId)'-
Muster und war NICHT fail-closed. Jetzt konsistent zu listAll:
- Controller: Portal-Token immer gescoped (ohne customerId -> []).
- Service getAllContracts: 'if (customerIds)' statt '.length > 0',
damit ein leeres Array strikt auf IN () filtert (0 Treffer) statt
durchzufallen. Einziger Caller ist der Contract-Controller ->
keine Regression fuer den Normalfall.
Verifiziert: Staff -> alle; Portal customerIds=[] -> 0 Vertraege/Belege.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -96,17 +96,20 @@ export async function getContracts(req: AuthRequest, res: Response): Promise<voi
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Für Kundenportal-Benutzer: nur eigene + vertretene Kunden MIT Vollmacht
|
// Für Kundenportal-Benutzer: nur eigene + vertretene Kunden MIT Vollmacht.
|
||||||
|
// Fail-closed (Pentest R145): Portal-Token wird IMMER gescoped; fehlt wider
|
||||||
|
// Erwarten die customerId, ergibt das eine leere Menge (nicht alle).
|
||||||
let customerIds: number[] | undefined;
|
let customerIds: number[] | undefined;
|
||||||
if (req.user?.isCustomerPortal && req.user.customerId) {
|
if (req.user?.isCustomerPortal) {
|
||||||
// Eigene Customer-ID immer
|
customerIds = req.user.customerId ? [req.user.customerId] : [];
|
||||||
customerIds = [req.user.customerId];
|
if (req.user.customerId) {
|
||||||
// Vertretene Kunden nur wenn Vollmacht erteilt
|
// Vertretene Kunden nur wenn Vollmacht erteilt
|
||||||
const representedIds: number[] = req.user.representedCustomerIds || [];
|
const representedIds: number[] = req.user.representedCustomerIds || [];
|
||||||
for (const repCustId of representedIds) {
|
for (const repCustId of representedIds) {
|
||||||
const hasAuth = await authorizationService.hasAuthorization(repCustId, req.user.customerId);
|
const hasAuth = await authorizationService.hasAuthorization(repCustId, req.user.customerId);
|
||||||
if (hasAuth) {
|
if (hasAuth) {
|
||||||
customerIds.push(repCustId);
|
customerIds.push(repCustId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,8 +66,8 @@ export async function listAll(req: AuthRequest, res: Response): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const page = parseInt((req.query.page as string) || '1') || 1;
|
const page = Math.max(parseInt((req.query.page as string) || '1') || 1, 1);
|
||||||
const limit = Math.min(parseInt((req.query.limit as string) || '50') || 50, 200);
|
const limit = Math.min(Math.max(parseInt((req.query.limit as string) || '50') || 50, 1), 200);
|
||||||
const search = typeof req.query.search === 'string' ? req.query.search : undefined;
|
const search = typeof req.query.search === 'string' ? req.query.search : undefined;
|
||||||
|
|
||||||
const result = await creditNoteService.getAllCreditNotes({ customerIds, page, limit, search });
|
const result = await creditNoteService.getAllCreditNotes({ customerIds, page, limit, search });
|
||||||
|
|||||||
@@ -21,8 +21,11 @@ export async function getAllContracts(filters: ContractFilters) {
|
|||||||
|
|
||||||
const where: Record<string, unknown> = {};
|
const where: Record<string, unknown> = {};
|
||||||
|
|
||||||
// Entweder einzelne customerId ODER Liste von customerIds (für Kundenportal)
|
// Entweder Liste von customerIds (Kundenportal, fail-closed) ODER einzelne
|
||||||
if (customerIds && customerIds.length > 0) {
|
// customerId (Staff-Filter). Fail-closed (Pentest R145): ist customerIds
|
||||||
|
// gesetzt – auch als LEERES Array – wird strikt darauf gefiltert (`IN ()`
|
||||||
|
// → 0 Treffer). Nur `undefined` (Staff) überspringt den Filter.
|
||||||
|
if (customerIds) {
|
||||||
where.customerId = { in: customerIds };
|
where.customerId = { in: customerIds };
|
||||||
} else if (customerId) {
|
} else if (customerId) {
|
||||||
where.customerId = customerId;
|
where.customerId = customerId;
|
||||||
|
|||||||
Reference in New Issue
Block a user