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:
parent
72dab94216
commit
44f1fc156c
@ -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);
|
||||||
|
@ -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);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user