mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-10-23 10:36:09 +00:00
🛠️ fix: Inclusive to date filter and reject inverted ranges
A `?to=2025-01-15` filter previously stopped at midnight UTC of that day, silently excluding everything that happened on January 15. The `parseIsoDate` helper now widens a bare `YYYY-MM-DD` to 23:59:59.999Z when called with the `end` boundary. Full ISO timestamps are honored exactly, so callers that want minute-precision can still get it. Also rejects inverted ranges (`from` later than `to`) with a 400 so operators see a clear error instead of a silent empty result.
This commit is contained in:
parent
d55a014af7
commit
ddc69efb04
2 changed files with 58 additions and 3 deletions
|
|
@ -184,6 +184,43 @@ describe('createAdminAuditLogHandlers', () => {
|
|||
expect(status).toHaveBeenCalledWith(200);
|
||||
});
|
||||
|
||||
it('widens a date-only `to` filter to the end of the day UTC', async () => {
|
||||
const deps = createDeps();
|
||||
const handlers = createAdminAuditLogHandlers(deps);
|
||||
const { req, res } = createReqRes({ query: { to: '2025-01-15' } });
|
||||
|
||||
await handlers.listAuditLog(req, res);
|
||||
|
||||
const filtersArg = (deps.listAuditLogPage as jest.Mock).mock.calls[0][1];
|
||||
expect(filtersArg.to).toBeInstanceOf(Date);
|
||||
expect((filtersArg.to as Date).toISOString()).toBe('2025-01-15T23:59:59.999Z');
|
||||
});
|
||||
|
||||
it('leaves a full ISO `to` timestamp exact (no day-boundary widening)', async () => {
|
||||
const deps = createDeps();
|
||||
const handlers = createAdminAuditLogHandlers(deps);
|
||||
const { req, res } = createReqRes({ query: { to: '2025-01-15T08:30:00Z' } });
|
||||
|
||||
await handlers.listAuditLog(req, res);
|
||||
|
||||
const filtersArg = (deps.listAuditLogPage as jest.Mock).mock.calls[0][1];
|
||||
expect((filtersArg.to as Date).toISOString()).toBe('2025-01-15T08:30:00.000Z');
|
||||
});
|
||||
|
||||
it('rejects when `from` is later than `to`', async () => {
|
||||
const deps = createDeps();
|
||||
const handlers = createAdminAuditLogHandlers(deps);
|
||||
const { req, res, status, json } = createReqRes({
|
||||
query: { from: '2025-12-31', to: '2025-01-01' },
|
||||
});
|
||||
|
||||
await handlers.listAuditLog(req, res);
|
||||
|
||||
expect(status).toHaveBeenCalledWith(400);
|
||||
expect(json.mock.calls[0][0].error).toMatch(/from.*to/);
|
||||
expect(deps.listAuditLogPage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('rejects limit > 500 with 400', async () => {
|
||||
const deps = createDeps();
|
||||
const handlers = createAdminAuditLogHandlers(deps);
|
||||
|
|
|
|||
|
|
@ -77,12 +77,27 @@ function parseActionFilter(raw: unknown):
|
|||
return { ok: true, value: arr as AuditAction[] };
|
||||
}
|
||||
|
||||
function parseIsoDate(raw: unknown): { ok: true; value?: Date } | { ok: false; error: string } {
|
||||
const DATE_ONLY_RE = /^\d{4}-\d{2}-\d{2}$/;
|
||||
|
||||
/**
|
||||
* Parses a filter date. `boundary` controls how a bare `YYYY-MM-DD` is widened:
|
||||
* `start` leaves it at the beginning of the day (00:00:00.000Z, the default for
|
||||
* `from`), `end` snaps it to 23:59:59.999Z so that a `to=2025-01-15` filter
|
||||
* actually includes everything that occurred on January 15 instead of cutting
|
||||
* off at midnight. Full ISO timestamps are honored exactly regardless.
|
||||
*/
|
||||
function parseIsoDate(
|
||||
raw: unknown,
|
||||
boundary: 'start' | 'end' = 'start',
|
||||
): { ok: true; value?: Date } | { ok: false; error: string } {
|
||||
if (raw == null || raw === '') return { ok: true, value: undefined };
|
||||
if (typeof raw !== 'string') return { ok: false, error: 'Date must be a string' };
|
||||
if (!ISO_DATE_RE.test(raw)) return { ok: false, error: 'Date must be ISO 8601' };
|
||||
const d = new Date(raw);
|
||||
if (Number.isNaN(d.getTime())) return { ok: false, error: 'Invalid date' };
|
||||
if (boundary === 'end' && DATE_ONLY_RE.test(raw)) {
|
||||
d.setUTCHours(23, 59, 59, 999);
|
||||
}
|
||||
return { ok: true, value: d };
|
||||
}
|
||||
|
||||
|
|
@ -208,10 +223,13 @@ function readTargetQuery(query: AuditLogQuery): string | undefined {
|
|||
function parseFilters(
|
||||
query: AuditLogQuery,
|
||||
): { ok: true; value: ParsedFilters } | { ok: false; error: string } {
|
||||
const from = parseIsoDate(query.from);
|
||||
const from = parseIsoDate(query.from, 'start');
|
||||
if (!from.ok) return { ok: false, error: `from: ${from.error}` };
|
||||
const to = parseIsoDate(query.to);
|
||||
const to = parseIsoDate(query.to, 'end');
|
||||
if (!to.ok) return { ok: false, error: `to: ${to.error}` };
|
||||
if (from.value && to.value && from.value > to.value) {
|
||||
return { ok: false, error: '`from` must be earlier than `to`' };
|
||||
}
|
||||
const action = parseActionFilter(query.action);
|
||||
if (!action.ok) return { ok: false, error: action.error };
|
||||
const targetPrincipalType = parsePrincipalType(query.targetPrincipalType);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue