1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-11 10:10:25 +01:00

[PM-11458] Bugfix - If two digit year was entered for card, the expired card message shows if card is not expired (#10801)

* normalize card expiry year before determining if it is expired

* add tests
This commit is contained in:
Jonathan Prusik 2024-09-04 13:39:48 -04:00 committed by GitHub
parent 72dab94216
commit 44f1fc156c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 94 additions and 43 deletions

View File

@ -24,7 +24,7 @@ import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folde
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherType } from "@bitwarden/common/vault/enums";
import { Launchable } from "@bitwarden/common/vault/interfaces/launchable"; import { Launchable } from "@bitwarden/common/vault/interfaces/launchable";
import { CardView } from "@bitwarden/common/vault/models/view/card.view"; import { isCardExpired } from "@bitwarden/common/vault/utils";
import { DialogService } from "@bitwarden/components"; import { DialogService } from "@bitwarden/components";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import { PasswordRepromptService } from "@bitwarden/vault"; import { PasswordRepromptService } from "@bitwarden/vault";
@ -123,7 +123,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
this.configService.getFeatureFlag$(FeatureFlag.ExtensionRefresh), this.configService.getFeatureFlag$(FeatureFlag.ExtensionRefresh),
); );
this.cardIsExpired = extensionRefreshEnabled && this.isCardExpiryInThePast(); this.cardIsExpired = extensionRefreshEnabled && isCardExpired(this.cipher.card);
} }
ngOnDestroy() { ngOnDestroy() {
@ -235,24 +235,6 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
this.viewingPasswordHistory = !this.viewingPasswordHistory; this.viewingPasswordHistory = !this.viewingPasswordHistory;
} }
isCardExpiryInThePast() {
if (this.cipher.card) {
const { expMonth, expYear }: CardView = this.cipher.card;
if (expYear && expMonth) {
// `Date` months are zero-indexed
const parsedMonth = parseInt(expMonth) - 1;
const parsedYear = parseInt(expYear);
// First day of the next month minus one, to get last day of the card month
const cardExpiry = new Date(parsedYear, parsedMonth + 1, 0);
const now = new Date();
return cardExpiry < now;
}
}
}
protected cleanUp() { protected cleanUp() {
if (this.totpInterval) { if (this.totpInterval) {
window.clearInterval(this.totpInterval); window.clearInterval(this.totpInterval);

View File

@ -1,4 +1,5 @@
import { normalizeExpiryYearFormat } from "@bitwarden/common/vault/utils"; import { CardView } from "@bitwarden/common/vault/models/view/card.view";
import { normalizeExpiryYearFormat, isCardExpired } from "@bitwarden/common/vault/utils";
function getExpiryYearValueFormats(currentCentury: string) { function getExpiryYearValueFormats(currentCentury: string) {
return [ return [
@ -72,3 +73,50 @@ describe("normalizeExpiryYearFormat", () => {
jest.clearAllTimers(); jest.clearAllTimers();
}); });
}); });
function getCardExpiryDateValues() {
const currentDate = new Date();
const currentYear = currentDate.getFullYear();
// `Date` months are zero-indexed, our expiry date month inputs are one-indexed
const currentMonth = currentDate.getMonth() + 1;
return [
[null, null, false], // no month, no year
[undefined, undefined, false], // no month, no year, invalid values
["", "", false], // no month, no year, invalid values
["12", "agdredg42grg35grrr. ea3534@#^145345ag$%^ -_#$rdg ", false], // invalid values
["0", `${currentYear - 1}`, true], // invalid 0 month
["00", `${currentYear + 1}`, false], // invalid 0 month
[`${currentMonth}`, "0000", true], // current month, in the year 2000
[null, `${currentYear}`.slice(-2), false], // no month, this year
[null, `${currentYear - 1}`.slice(-2), true], // no month, last year
["1", null, false], // no year, January
["1", `${currentYear - 1}`, true], // January last year
["13", `${currentYear}`, false], // 12 + 1 is Feb. in the next year (Date is zero-indexed)
[`${currentMonth + 36}`, `${currentYear - 1}`, true], // even though the month value would put the date 3 years into the future when calculated with `Date`, an explicit year in the past indicates the card is expired
[`${currentMonth}`, `${currentYear}`, false], // this year, this month (not expired until the month is over)
[`${currentMonth}`, `${currentYear}`.slice(-2), false], // This month, this year (not expired until the month is over)
[`${currentMonth - 1}`, `${currentYear}`, true], // last month
[`${currentMonth - 1}`, `${currentYear + 1}`, false], // 11 months from now
];
}
describe("isCardExpired", () => {
const expiryYearValueFormats = getCardExpiryDateValues();
expiryYearValueFormats.forEach(
([inputMonth, inputYear, expectedValue]: [string | null, string | null, boolean]) => {
it(`should return ${expectedValue} when the card expiry month is ${inputMonth} and the card expiry year is ${inputYear}`, () => {
const testCardView = new CardView();
testCardView.expMonth = inputMonth;
testCardView.expYear = inputYear;
const cardIsExpired = isCardExpired(testCardView);
expect(cardIsExpired).toBe(expectedValue);
});
},
);
});

View File

@ -1,3 +1,5 @@
import { CardView } from "@bitwarden/common/vault/models/view/card.view";
type NonZeroIntegers = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; type NonZeroIntegers = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
type Year = `${NonZeroIntegers}${NonZeroIntegers}${0 | NonZeroIntegers}${0 | NonZeroIntegers}`; type Year = `${NonZeroIntegers}${NonZeroIntegers}${0 | NonZeroIntegers}${0 | NonZeroIntegers}`;
@ -40,3 +42,42 @@ export function normalizeExpiryYearFormat(yearInput: string | number): Year | nu
return expirationYear as Year | null; return expirationYear as Year | null;
} }
/**
* Takes a cipher card view and returns "true" if the month and year affirmativey indicate
* the card is expired.
*
* @export
* @param {CardView} cipherCard
* @return {*} {boolean}
*/
export function isCardExpired(cipherCard: CardView): boolean {
if (cipherCard) {
const { expMonth = null, expYear = null } = cipherCard;
const now = new Date();
const normalizedYear = normalizeExpiryYearFormat(expYear);
// If the card year is before the current year, don't bother checking the month
if (normalizedYear && parseInt(normalizedYear) < now.getFullYear()) {
return true;
}
if (normalizedYear && expMonth) {
// `Date` months are zero-indexed
const parsedMonth =
parseInt(expMonth) - 1 ||
// Add a month floor of 0 to protect against an invalid low month value of "0"
0;
const parsedYear = parseInt(normalizedYear);
// First day of the next month minus one, to get last day of the card month
const cardExpiry = new Date(parsedYear, parsedMonth + 1, 0);
return cardExpiry < now;
}
}
return false;
}

View File

@ -8,10 +8,10 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
import { CollectionId } from "@bitwarden/common/types/guid"; import { CollectionId } from "@bitwarden/common/types/guid";
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { CardView } from "@bitwarden/common/vault/models/view/card.view";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { isCardExpired } from "@bitwarden/common/vault/utils";
import { SearchModule, CalloutModule } from "@bitwarden/components"; import { SearchModule, CalloutModule } from "@bitwarden/components";
import { AdditionalOptionsComponent } from "./additional-options/additional-options.component"; import { AdditionalOptionsComponent } from "./additional-options/additional-options.component";
@ -61,7 +61,7 @@ export class CipherViewComponent implements OnInit, OnDestroy {
async ngOnInit() { async ngOnInit() {
await this.loadCipherData(); await this.loadCipherData();
this.cardIsExpired = this.isCardExpiryInThePast(); this.cardIsExpired = isCardExpired(this.cipher.card);
} }
ngOnDestroy(): void { ngOnDestroy(): void {
@ -102,24 +102,4 @@ export class CipherViewComponent implements OnInit, OnDestroy {
.pipe(takeUntil(this.destroyed$)); .pipe(takeUntil(this.destroyed$));
} }
} }
isCardExpiryInThePast() {
if (this.cipher.card) {
const { expMonth, expYear }: CardView = this.cipher.card;
if (expYear && expMonth) {
// `Date` months are zero-indexed
const parsedMonth = parseInt(expMonth) - 1;
const parsedYear = parseInt(expYear);
// First day of the next month minus one, to get last day of the card month
const cardExpiry = new Date(parsedYear, parsedMonth + 1, 0);
const now = new Date();
return cardExpiry < now;
}
}
return false;
}
} }