From b1b05ff2c531c21b5be34bcdfce0478dc324c57f Mon Sep 17 00:00:00 2001 From: Mok Date: Sun, 11 Jul 2021 11:48:30 +0800 Subject: [PATCH] Added multiple date format support for field --- .nsprc.sample | 10 +++++ CHANGELOG.md | 1 + README.md | 10 ++--- src/types/date.d.ts | 4 ++ src/types/index.d.ts | 1 + src/types/nsprc.d.ts | 2 +- src/utils/date.ts | 29 +++++++++++++ src/utils/vulnerability.ts | 9 ++-- test/__mocks__/nsprc.json | 4 +- test/utils/date.test.ts | 70 ++++++++++++++++++++++++++++++++ test/utils/vulnerability.test.ts | 16 ++++---- 11 files changed, 136 insertions(+), 20 deletions(-) create mode 100644 src/types/date.d.ts create mode 100644 src/utils/date.ts create mode 100644 test/utils/date.test.ts diff --git a/.nsprc.sample b/.nsprc.sample index 1bd78fd..8a09e5e 100644 --- a/.nsprc.sample +++ b/.nsprc.sample @@ -4,6 +4,16 @@ "notes": "Ignored since we don't use xxx method", "expiry": 1615462134681 }, + "1338": { + "active": true, + "expiry": "01/31/2021, 11:03:58" + }, + "1339": { + "expiry": "Sun, 11 Jul 2021 03:03:13 GMT" + }, + "1340": { + "expiry": "Thu Jan 26 2017 11:00:00 GMT+1100 (Australian Eastern Daylight Time)" + }, "4501": { "active": false, "notes": "This will be fixed by the library maintainers by June 14" diff --git a/CHANGELOG.md b/CHANGELOG.md index 55ba7b1..2cdb441 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## 3.1.0 (July 11, 2021) - Updated declaration file extension so it will not be included in final build +- Added multiple date format support for `expiry` field ## 3.0.1 (July 11, 2021) diff --git a/README.md b/README.md index bdd934f..5531e36 100644 --- a/README.md +++ b/README.md @@ -110,11 +110,11 @@ You may add a file `.nsprc` to your project root directory to manage the excepti ### Fields -| Attribute | Description | Default | -| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -| `active` | Boolean type to determine if we should use it for exception; `true` or `false` | `true` | -| `expiry` | Date time in milliseconds, the number of milliseconds since midnight 01 January, 1970 UTC.
You can use `new Date(2021, 1, 1).valueOf()` to get the milliseconds value. | | -| `notes` | Notes related to the vulnerability; will be displayed in the table summary. | +| Attribute | Type | Description | Default | Examples | +| --------- | ---------------- | --------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `active` | Boolean | If the tool should use it for exception | `true` | `true` | +| `expiry` | String \| Number | UNIX timestamp | | - `'2020-01-31'`
- `'2020/01/31'`
- `'01/31/2021, 11:03:58'`
- `'1 March 2016 15:00'`
- `'1 March 2016 3:00 pm'`
- `'2012-01-26T13:51:50.417-07:00'`
- `'Sun, 11 Jul 2021 03:03:13 GMT'`
- `'Thu Jan 26 2017 11:00:00 GMT+1100 (Australian Eastern Daylight Time)'`
- `327611110417` | +| `notes` | String | Notes related to the vulnerability. | |
diff --git a/src/types/date.d.ts b/src/types/date.d.ts new file mode 100644 index 0000000..9d27fbc --- /dev/null +++ b/src/types/date.d.ts @@ -0,0 +1,4 @@ +export type DateAnalysis = { + valid: boolean; + expired?: boolean; +}; diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 39e1b27..dc7b026 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -1,4 +1,5 @@ export * from './color'; +export * from './date'; export * from './general'; export * from './level'; export * from './nsprc'; diff --git a/src/types/nsprc.d.ts b/src/types/nsprc.d.ts index 67ed51e..ef5501b 100644 --- a/src/types/nsprc.d.ts +++ b/src/types/nsprc.d.ts @@ -1,6 +1,6 @@ export interface NsprcConfigs { readonly active?: boolean; - readonly expiry?: number; + readonly expiry?: string | number; readonly notes?: string; } diff --git a/src/utils/date.ts b/src/utils/date.ts new file mode 100644 index 0000000..a05646f --- /dev/null +++ b/src/utils/date.ts @@ -0,0 +1,29 @@ +import { DateAnalysis } from 'src/types'; + +/** + * Validate if the given timestamp is a valid UNIX timestamp + * @param {Any} timestamp The given timestamp + * @return {Boolean} Returns true if it is a valid UNIX timestamp + */ +export function isValidDate(timestamp: string | number): boolean { + return new Date(timestamp).getTime() > 0; +} + +/** + * Analyze the given date time if it has expired (in the past) + * @param {String | Number} expiry Expiry timestamp + * @param {String | Number} now The date to compare with + * @return {Object} Return the analysis + */ +export function analyzeExpiry(expiry?: string | number, now: string | number = new Date().valueOf()): DateAnalysis { + if (!expiry) { + return { valid: true }; + } + if (!isValidDate(expiry) || !isValidDate(now)) { + return { valid: false }; + } + return { + valid: true, + expired: new Date(now).getTime() > new Date(expiry).getTime(), + }; +} diff --git a/src/utils/vulnerability.ts b/src/utils/vulnerability.ts index d58c22b..436b9ad 100644 --- a/src/utils/vulnerability.ts +++ b/src/utils/vulnerability.ts @@ -3,6 +3,7 @@ import get from 'lodash.get'; import { isJsonString } from './common'; import { color, getSeverityBgColor } from './color'; import { printExceptionReport } from './print'; +import { analyzeExpiry } from './date'; import { NpmAuditJson, @@ -187,13 +188,13 @@ export function processExceptions(nsprc: NsprcFile, cmdExceptions: number[] = [] const isValidId = !isNaN(numberId); const isActive = Boolean(get(details, 'active', true)); // default to true const expiryDate = get(details, 'expiry') ? new Date(get(details, 'expiry')).toUTCString() : ''; - const hasExpired = get(details, 'expiry') ? get(details, 'expiry') < new Date(Date.now()).getTime() : false; const notes = typeof details === 'string' ? details : get(details, 'notes', ''); + const { valid, expired } = analyzeExpiry(get(details, 'expiry')); let status = color('active', 'green'); - if (hasExpired) { + if (expired) { status = color('expired', 'red'); - } else if (!isValidId) { + } else if (!isValidId || !valid) { status = color('invalid', 'red'); } else if (!isActive) { status = color('inactive', 'yellow'); @@ -201,7 +202,7 @@ export function processExceptions(nsprc: NsprcFile, cmdExceptions: number[] = [] acc.report.push([id, status, expiryDate, notes]); - if (isValidId && isActive && !hasExpired) { + if (isValidId && isActive && !expired) { acc.exceptionIds.push(numberId); } diff --git a/test/__mocks__/nsprc.json b/test/__mocks__/nsprc.json index 8153510..ee7cf1f 100644 --- a/test/__mocks__/nsprc.json +++ b/test/__mocks__/nsprc.json @@ -10,12 +10,12 @@ "active": true }, "1556": { - "expiry": 1615462134681, + "expiry": "2021-03-11T11:28:54.681Z", "active": true, "notes": "Issue: https://github.com/jeemok/better-npm-audit/issues/28" }, "975": { - "expiry": 1615462134681 + "expiry": "Thu Mar 11 2021 19:28:54 GMT+0800 (Malaysia Time)" }, "976": { "active": false diff --git a/test/utils/date.test.ts b/test/utils/date.test.ts new file mode 100644 index 0000000..c94a934 --- /dev/null +++ b/test/utils/date.test.ts @@ -0,0 +1,70 @@ +import { expect } from 'chai'; +import { isValidDate, analyzeExpiry } from '../../src/utils/date'; + +describe('Date utils', () => { + describe('#isValidDate', () => { + it('should be able to determine a valid UNIX timestamp correctly', () => { + // Valid cases + expect(isValidDate('2020-01-31')).to.equal(true); + expect(isValidDate('2020/01/31')).to.equal(true); + expect(isValidDate('01/31/2021')).to.equal(true); + expect(isValidDate('01/31/2021, 11:03:58')).to.equal(true); + expect(isValidDate('1 March 2016 15:00')).to.equal(true); + expect(isValidDate('1 March 2016 3:00 pm')).to.equal(true); + expect(isValidDate('1 Mar 2016')).to.equal(true); + expect(isValidDate('1 March 2016')).to.equal(true); + expect(isValidDate('March 1 2016')).to.equal(true); + expect(isValidDate('Julai 1 2016')).to.equal(true); + expect(isValidDate('Julei 1 2016')).to.equal(true); + expect(isValidDate('Jul 11 2021')).to.equal(true); + expect(isValidDate('Sun Jul 11 2021')).to.equal(true); + expect(isValidDate('2020-01-01T00:00:00')).to.equal(true); + expect(isValidDate('2020-01-01T00:00:00Z')).to.equal(true); + expect(isValidDate('2020-01-01T00:00:00.000Z')).to.equal(true); + expect(isValidDate('2012-01-26T13:51:50.417-07:00')).to.equal(true); + expect(isValidDate('Sun, 11 Jul 2021 03:03:13 GMT')).to.equal(true); + expect(isValidDate('Thu Jan 26 2017 11:00:00 GMT+1100 (Australian Eastern Daylight Time)')).to.equal(true); + expect(isValidDate('Wed Jan 25 2017 16:00:00 GMT-0800 (Pacific Standard Time)')).to.equal(true); + expect(isValidDate(1327611110417)).to.equal(true); + + // Invalid cases + expect(isValidDate('1327611110417')).to.equal(false); + expect(isValidDate('2020-31-01')).to.equal(false); + expect(isValidDate('2020/31/01')).to.equal(false); + expect(isValidDate('31/01/2021')).to.equal(false); + expect(isValidDate('31-01-2021')).to.equal(false); + expect(isValidDate('Unknown 1 2016')).to.equal(false); + expect(isValidDate('2020-01-01T00:000:00')).to.equal(false); + expect(isValidDate('11:03:58')).to.equal(false); + }); + }); + + describe('#analyzeExpiry', () => { + it('should return valid and not expired if not given any date', () => { + expect(analyzeExpiry()).to.deep.equal({ valid: true }); + }); + + it('should be able to detect invalid dates', () => { + expect(analyzeExpiry('2020-01-32', '2020-02-02')).to.deep.equal({ valid: false }); + expect(analyzeExpiry('2020-01-30', '2020-13-02')).to.deep.equal({ valid: false }); + expect(analyzeExpiry('2020-01-50')).to.deep.equal({ valid: false }); + }); + + it('should be able to analyze the given timestamp correctly', () => { + // Only dates + expect(analyzeExpiry('2020-01-31', '2020-01-01')).to.deep.equal({ valid: true, expired: false }); + expect(analyzeExpiry('2020-01-31', '2020-01-31')).to.deep.equal({ valid: true, expired: false }); + expect(analyzeExpiry('2020-01-31', '2020-02-02')).to.deep.equal({ valid: true, expired: true }); + + // Dates & time + expect(analyzeExpiry('1 March 2020 3:00 pm', '1 March 2020 2:59:00 pm')).to.deep.equal({ valid: true, expired: false }); + expect(analyzeExpiry('1 March 2020 3:00 pm', '1 March 2020 3:00:00 pm')).to.deep.equal({ valid: true, expired: false }); + expect(analyzeExpiry('1 March 2020 3:00 pm', '1 March 2020 3:00:01 pm')).to.deep.equal({ valid: true, expired: true }); + + // Milliseconds + expect(analyzeExpiry(1327611110410, 1327611110409)).to.deep.equal({ valid: true, expired: false }); + expect(analyzeExpiry(1327611110410, 1327611110410)).to.deep.equal({ valid: true, expired: false }); + expect(analyzeExpiry(1327611110410, 1327611110411)).to.deep.equal({ valid: true, expired: true }); + }); + }); +}); diff --git a/test/utils/vulnerability.test.ts b/test/utils/vulnerability.test.ts index 7f3a9be..6e6d8ec 100644 --- a/test/utils/vulnerability.test.ts +++ b/test/utils/vulnerability.test.ts @@ -62,8 +62,8 @@ describe('Vulnerability utils', () => { expect(result).to.have.property('report'); const activeExceptionIds = result.report - .filter((exception: Array) => exception[1] === '\u001b[32mactive\u001b[0m') - .map((each: Array) => Number(each[0])); + .filter((exception: string[]) => exception[1] === '\u001b[32mactive\u001b[0m') + .map((each: (string | number)[]) => Number(each[0])); expect(activeExceptionIds).to.have.length(4).to.deep.equal([985, 1213, 1654, 2100]); }); @@ -73,8 +73,8 @@ describe('Vulnerability utils', () => { expect(result).to.have.property('report'); const activeExceptionIds = result.report - .filter((exception: Array) => exception[1] === '\u001b[33minactive\u001b[0m') - .map((each: Array) => Number(each[0])); + .filter((exception: string[]) => exception[1] === '\u001b[33minactive\u001b[0m') + .map((each: (string | number)[]) => Number(each[0])); expect(activeExceptionIds).to.have.length(1).to.deep.equal([976]); }); @@ -85,8 +85,8 @@ describe('Vulnerability utils', () => { expect(result).to.have.property('report'); const activeExceptionIds = result.report - .filter((exception: Array) => exception[1] === '\u001b[31mexpired\u001b[0m') - .map((each: Array) => Number(each[0])); + .filter((exception: string[]) => exception[1] === '\u001b[31mexpired\u001b[0m') + .map((each: (string | number)[]) => Number(each[0])); expect(activeExceptionIds).to.have.length(5).to.deep.equal([975, 1084, 1179, 1556, 1651]); // Clean up @@ -99,8 +99,8 @@ describe('Vulnerability utils', () => { expect(result).to.have.property('report'); const activeExceptionIds = result.report - .filter((exception: Array) => exception[1] === '\u001b[31minvalid\u001b[0m') - .map((each: Array) => each[0]); + .filter((exception: string[]) => exception[1] === '\u001b[31minvalid\u001b[0m') + .map((each: (string | number)[]) => each[0]); expect(activeExceptionIds).to.have.length(1).to.deep.equal(['Note']); }); });