mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 13:53:34 +00:00
[PM-2256] Fix importer parsing credit card expiry year (#5444)
* Fix importer parsing credit card expiry year When importing a credit card from Enpass it was found that with a 4 digit expiry year was prefixed with '20', stored at 11/202025 instead of 11/2025. Fixed typo that checked length of month instead of year which incorrectly added prefix. * Refactor setCardExpiration to use RegExp
This commit is contained in:
107
libs/importer/spec/base-importer.spec.ts
Normal file
107
libs/importer/spec/base-importer.spec.ts
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
||||||
|
import { CardView } from "@bitwarden/common/vault/models/view/card.view";
|
||||||
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
|
|
||||||
|
import { BaseImporter } from "../src/importers/base-importer";
|
||||||
|
|
||||||
|
class FakeBaseImporter extends BaseImporter {
|
||||||
|
initLoginCipher(): CipherView {
|
||||||
|
return super.initLoginCipher();
|
||||||
|
}
|
||||||
|
|
||||||
|
setCardExpiration(cipher: CipherView, expiration: string): boolean {
|
||||||
|
return super.setCardExpiration(cipher, expiration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("BaseImporter class", () => {
|
||||||
|
const importer = new FakeBaseImporter();
|
||||||
|
let cipher: CipherView;
|
||||||
|
|
||||||
|
describe("setCardExpiration method", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cipher = importer.initLoginCipher();
|
||||||
|
cipher.card = new CardView();
|
||||||
|
cipher.type = CipherType.Card;
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
["01/2025", "1", "2025"],
|
||||||
|
["5/21", "5", "2021"],
|
||||||
|
["10/2100", "10", "2100"],
|
||||||
|
])(
|
||||||
|
"sets ciper card expYear & expMonth and returns true",
|
||||||
|
(expiration, expectedMonth, expectedYear) => {
|
||||||
|
const result = importer.setCardExpiration(cipher, expiration);
|
||||||
|
expect(cipher.card.expMonth).toBe(expectedMonth);
|
||||||
|
expect(cipher.card.expYear).toBe(expectedYear);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
["01/2032", "1"],
|
||||||
|
["09/2032", "9"],
|
||||||
|
["10/2032", "10"],
|
||||||
|
])("removes leading zero from month", (expiration, expectedMonth) => {
|
||||||
|
const result = importer.setCardExpiration(cipher, expiration);
|
||||||
|
expect(cipher.card.expMonth).toBe(expectedMonth);
|
||||||
|
expect(cipher.card.expYear).toBe("2032");
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
["12/00", "2000"],
|
||||||
|
["12/99", "2099"],
|
||||||
|
["12/32", "2032"],
|
||||||
|
["12/2042", "2042"],
|
||||||
|
])("prefixes '20' to year if only two digits long", (expiration, expectedYear) => {
|
||||||
|
const result = importer.setCardExpiration(cipher, expiration);
|
||||||
|
expect(cipher.card.expYear).toHaveLength(4);
|
||||||
|
expect(cipher.card.expYear).toBe(expectedYear);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([["01 / 2025"], ["01 / 2025"], [" 01/2025 "], [" 01/2025 "]])(
|
||||||
|
"removes any whitespace in expiration string",
|
||||||
|
(expiration) => {
|
||||||
|
const result = importer.setCardExpiration(cipher, expiration);
|
||||||
|
expect(cipher.card.expMonth).toBe("1");
|
||||||
|
expect(cipher.card.expYear).toBe("2025");
|
||||||
|
expect(result).toBe(true);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
it.each([[""], [" "], [null]])(
|
||||||
|
"returns false if expiration is null or empty ",
|
||||||
|
(expiration) => {
|
||||||
|
const result = importer.setCardExpiration(cipher, expiration);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
it.each([["0123"], ["01/03/23"]])(
|
||||||
|
"returns false if invalid card expiration string",
|
||||||
|
(expiration) => {
|
||||||
|
const result = importer.setCardExpiration(cipher, expiration);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
it.each([["5/"], ["03/231"], ["12/1"], ["2/20221"]])(
|
||||||
|
"returns false if year is not 2 or 4 digits long",
|
||||||
|
(expiration) => {
|
||||||
|
const result = importer.setCardExpiration(cipher, expiration);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
it.each([["/2023"], ["003/2023"], ["111/32"]])(
|
||||||
|
"returns false if month is not 1 or 2 digits long",
|
||||||
|
(expiration) => {
|
||||||
|
const result = importer.setCardExpiration(cipher, expiration);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -100,7 +100,7 @@ describe("Enpass JSON Importer", () => {
|
|||||||
expect(cipher.card.brand).toEqual("Amex");
|
expect(cipher.card.brand).toEqual("Amex");
|
||||||
expect(cipher.card.code).toEqual("1234");
|
expect(cipher.card.code).toEqual("1234");
|
||||||
expect(cipher.card.expMonth).toEqual("3");
|
expect(cipher.card.expMonth).toEqual("3");
|
||||||
expect(cipher.card.expYear).toEqual("23");
|
expect(cipher.card.expYear).toEqual("2023");
|
||||||
|
|
||||||
// remaining fields as custom fields
|
// remaining fields as custom fields
|
||||||
expect(cipher.fields.length).toEqual(9);
|
expect(cipher.fields.length).toEqual(9);
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ function expectCreditCard(cipher: CipherView) {
|
|||||||
expect(cipher.card.number).toBe("4024007103939509");
|
expect(cipher.card.number).toBe("4024007103939509");
|
||||||
expect(cipher.card.code).toBe("123");
|
expect(cipher.card.code).toBe("123");
|
||||||
expect(cipher.card.expMonth).toBe("1");
|
expect(cipher.card.expMonth).toBe("1");
|
||||||
expect(cipher.card.expYear).toBe("22");
|
expect(cipher.card.expYear).toBe("2022");
|
||||||
}
|
}
|
||||||
|
|
||||||
function expectIdentity(cipher: CipherView) {
|
function expectIdentity(cipher: CipherView) {
|
||||||
|
|||||||
@@ -305,29 +305,26 @@ export abstract class BaseImporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected setCardExpiration(cipher: CipherView, expiration: string): boolean {
|
protected setCardExpiration(cipher: CipherView, expiration: string): boolean {
|
||||||
if (!this.isNullOrWhitespace(expiration)) {
|
if (this.isNullOrWhitespace(expiration)) {
|
||||||
expiration = expiration.replace(/\s/g, "");
|
return false;
|
||||||
const parts = expiration.split("/");
|
|
||||||
if (parts.length === 2) {
|
|
||||||
let month: string = null;
|
|
||||||
let year: string = null;
|
|
||||||
if (parts[0].length === 1 || parts[0].length === 2) {
|
|
||||||
month = parts[0];
|
|
||||||
if (month.length === 2 && month[0] === "0") {
|
|
||||||
month = month.substr(1, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (parts[1].length === 2 || parts[1].length === 4) {
|
|
||||||
year = month.length === 2 ? "20" + parts[1] : parts[1];
|
|
||||||
}
|
|
||||||
if (month != null && year != null) {
|
|
||||||
cipher.card.expMonth = month;
|
|
||||||
cipher.card.expYear = year;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
expiration = expiration.replace(/\s/g, "");
|
||||||
|
|
||||||
|
const monthRegex = "0?(?<month>[1-9]|1[0-2])";
|
||||||
|
const yearRegex = "(?<year>(?:[1-2][0-9])?[0-9]{2})";
|
||||||
|
const expiryRegex = new RegExp(`^${monthRegex}/${yearRegex}$`);
|
||||||
|
|
||||||
|
const expiryMatch = expiration.match(expiryRegex);
|
||||||
|
|
||||||
|
if (!expiryMatch) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cipher.card.expMonth = expiryMatch.groups.month;
|
||||||
|
const year: string = expiryMatch.groups.year;
|
||||||
|
cipher.card.expYear = year.length === 2 ? "20" + year : year;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected moveFoldersToCollections(result: ImportResult) {
|
protected moveFoldersToCollections(result: ImportResult) {
|
||||||
|
|||||||
Reference in New Issue
Block a user