diff --git a/index.ts b/index.ts index 10e507c..f087ce4 100644 --- a/index.ts +++ b/index.ts @@ -1 +1,2 @@ export { redactIt } from "./src/redact-it"; +export * from "./typings"; diff --git a/src/redact-it.ts b/src/redact-it.ts index f23116e..deb5638 100644 --- a/src/redact-it.ts +++ b/src/redact-it.ts @@ -1,18 +1,20 @@ import { Mask, RedactIt, - RedacItConfig, + RedactItConfig, ReplacerFunction, PercentageMask, + CenterPercentageMask, } from "../typings"; const percentageValueMasker = ( value: any, - mask: PercentageMask + mask: PercentageMask | CenterPercentageMask ): string | undefined => { const redactor = mask.redactWith ?? "•"; const percentage = mask.percentage ?? 100; - const complementary = mask.complementary ?? false; + const complementary = + (mask.position === "center" && mask.complementary) ?? false; const position = mask.position ?? "left"; const finalRedactor = (p1: string): string => @@ -29,6 +31,9 @@ const percentageValueMasker = ( if (position === "center") { return `(.{${unmaskedLength / 2}})(.{${maskedLength}})(.+)`; } + if (position === "right") { + return `(.{${unmaskedLength}})(.{${maskedLength}})`; + } return `(.{${maskedLength}})(.{${unmaskedLength}})`; }; @@ -36,14 +41,18 @@ const percentageValueMasker = ( const masoq = (_match: any, p1: string, p2: string, p3?: string): string => { if (p3 && complementary) { + // Center + complementary return `${finalRedactor(p1)}${p2}${finalRedactor(p3)}`; } if (p3) { + // Center return `${p1}${finalRedactor(p2)}${p3}`; } - if (complementary || (position === "right" && !complementary)) { + if (position === "right") { + // Right return `${p1}${finalRedactor(p2)}`; } + // Left return `${finalRedactor(p1)}${p2}`; }; @@ -53,25 +62,25 @@ const percentageValueMasker = ( }; export const redactIt: RedactIt = ( - configs?: RedacItConfig | RedacItConfig[] + configs?: RedactItConfig | RedactItConfig[] ): ReplacerFunction => { const defaultMask: Mask = { type: "replace", redactWith: "[redacted]", }; - const defaultOptions: RedacItConfig = { + const defaultOptions: RedactItConfig = { fields: ["password"], mask: defaultMask, }; const mappedFields: Map = new Map(); - const optionsArray: RedacItConfig[] = Array.isArray(configs) + const optionsArray: RedactItConfig[] = Array.isArray(configs) ? configs : [configs ?? defaultOptions]; - optionsArray.forEach((option: RedacItConfig) => { + optionsArray.forEach((option: RedactItConfig) => { option.fields.forEach((field) => { mappedFields.set(field, option.mask ?? defaultMask); }); diff --git a/test/unit/redact-it.test.ts b/test/unit/redact-it.test.ts index 2327f36..56b0b6c 100644 --- a/test/unit/redact-it.test.ts +++ b/test/unit/redact-it.test.ts @@ -129,39 +129,41 @@ describe("Redact-it - Single configs argument", () => { expect(JSON.parse(stringResult).card.number).to.be.eq("************4321"); }); - it("should redact the last 4 digits of a 16 digits value when a 75% complementary percentage mask is used", async () => { + it("should redact the middle digits when position center is used", async () => { const myData = { ...defaultObject }; const replacerFunction: ReplacerFunction = redactIt({ - fields: ["number"], + fields: ["email"], mask: { type: "percentage", redactWith: "*", - percentage: 75, - complementary: true, + percentage: 50, + position: "center", }, }); const stringResult = JSON.stringify(myData, replacerFunction); - expect(JSON.parse(stringResult).card.number).to.be.eq("123456788765****"); + expect(JSON.parse(stringResult).email).to.be.eq( + "foo123*************ar.com" + ); }); - it("should redact the middle digits when position center is used", async () => { + it("should redact the last digits when position right is used", async () => { const myData = { ...defaultObject }; const replacerFunction: ReplacerFunction = redactIt({ fields: ["email"], mask: { type: "percentage", redactWith: "*", - percentage: 50, - position: "center", + percentage: 75, + position: "right", }, }); const stringResult = JSON.stringify(myData, replacerFunction); expect(JSON.parse(stringResult).email).to.be.eq( - "foo123*************ar.com" + "foo123*******************" ); }); @@ -300,4 +302,30 @@ describe("Redact-it - Multiple configs argument", () => { authorization: "•••••case", }); }); + + it("overrides configs for same field", () => { + const myData = { ...defaultObject }; + const replacerFunction = redactIt([ + { + fields: ["email", "name"], + mask: { + type: "replace", + redactWith: "firstRule", + }, + }, + { + fields: ["email"], + mask: { + type: "replace", + redactWith: "secondRule", + }, + }, + ]); + + const stringResult = JSON.stringify(myData, replacerFunction); + const parsedResult = JSON.parse(stringResult); + + expect(parsedResult.name).to.be.equal("firstRule"); + expect(parsedResult.email).to.be.equal("secondRule"); + }); }); diff --git a/typings/index.ts b/typings/index.ts index d16311b..42a38f7 100644 --- a/typings/index.ts +++ b/typings/index.ts @@ -1,45 +1,77 @@ -/** - * Mask options - * @param {string} type - Type of the mask to be applied - * @param {string} redactWith - Character or word to be used as the redacted part - * @param {number} percentage - Percentage of the value to apply the mask on - * @param {boolean} complementary - Whether the complemetary part of the percentage should be masked - */ -export type Mask = PercentageMask | UndefineMask | ReplaceMask; +export type Mask = + | PercentageMask + | CenterPercentageMask + | UndefineMask + | ReplaceMask; export interface PercentageMask { type: "percentage"; - redactWith?: "*" | "•" | "[redacted]" | string; + /** + * Character to be used as the redacted part + * @default "•" + */ + redactWith?: "*" | "•" | string; + /** + * Percentage of the value to apply the mask on + * @default 100 + */ percentage?: number; + /** + * Which part of the value to redact + * @default "left" + */ + position?: "left" | "right"; +} + +export interface CenterPercentageMask { + type: "percentage"; + position: "center"; + /** + * Character to be used as the redacted part + * @default "•" + */ + redactWith?: "*" | "•" | string; + /** + * Percentage of the value to apply the mask on + * @default 100 + */ + percentage?: number; + /** + * Whether the complementary part of the percentage should be masked + * @default false + */ complementary?: boolean; - position?: "left" | "center" | "right"; } + export interface UndefineMask { type: "undefine"; } export interface ReplaceMask { type: "replace"; + /** + * Replace the value entirely with this string + * @default "[redacted]" + */ redactWith: "[redacted]" | string; } -/** - * Redact-it configs to customize how and which fields are going to be redacted - * @param {string[]} fields - Field names to redact - * @param {Mask} mask - Which mask to apply - */ -export interface RedacItConfig { +/** Redact-it configs to customize how and which fields are going to be redacted */ +export interface RedactItConfig { + /** Field names to redact */ fields: (string | RegExp)[]; + /** Which mask to apply */ mask?: Mask; } +/** A replacer function compatible with JSON.stringify */ export type ReplacerFunction = (key: any, value: any) => any; /** * A function that takes the argument and creates a replacer function - * The arguement may be a single object of the RedacItConfig type or an array of these objects - * @param {RedacItConfig | RedacItConfig[]} configs - RedacItConfig to customize the redact function - * @param {function} replacer - A replacer function compatible with JSON.stringify + * The argument may be a single object of the RedactItConfig type or an array of these objects + * @param {RedactItConfig | RedactItConfig[]} configs - RedactItConfig to customize the redact function + * @return {ReplacerFunction} replacer - A replacer function compatible with JSON.stringify */ export type RedactIt = ( - configs?: RedacItConfig | RedacItConfig[] + configs?: RedactItConfig | RedactItConfig[] ) => ReplacerFunction;