[BEEEP][PM-3876] - Warn users if a stored card has an expiry date in the past (#10470)

* add enableExpiredPaymentCipherWarning setting

* add card expiry warning to the v2 Card Details component

* remove enableExpiredPaymentCipherWarning setting

* update expired card callout design and copy

* move card expired callout to cipher view

* add card expiry warning to the web vault add-edit cipher component
This commit is contained in:
Jonathan Prusik 2024-08-21 10:59:03 -04:00 committed by GitHub
parent b030c6e27b
commit 1fe6631c82
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 72 additions and 1 deletions

View File

@ -3960,6 +3960,12 @@
"autoFillOnPageLoad": {
"message": "Autofill on page load?"
},
"cardExpiredTitle": {
"message": "Expired card"
},
"cardExpiredMessage": {
"message": "If you've renewed it, update the card's information"
},
"cardDetails": {
"message": "Card details"
},

View File

@ -23,6 +23,9 @@
<bit-callout type="info" *ngIf="allowOwnershipAssignment() && !allowPersonal">
{{ "personalOwnershipPolicyInEffect" | i18n }}
</bit-callout>
<bit-callout *ngIf="cardIsExpired" type="info" [title]="'cardExpiredTitle' | i18n">
{{ "cardExpiredMessage" | i18n }}
</bit-callout>
<div class="row" *ngIf="!editMode && !viewOnly">
<div class="col-6 form-group">
<label for="type">{{ "whatTypeOfItem" | i18n }}</label>

View File

@ -11,6 +11,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { ProductTierType } from "@bitwarden/common/billing/enums";
import { EventType } from "@bitwarden/common/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@ -23,6 +24,7 @@ import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folde
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { Launchable } from "@bitwarden/common/vault/interfaces/launchable";
import { CardView } from "@bitwarden/common/vault/models/view/card.view";
import { DialogService } from "@bitwarden/components";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import { PasswordRepromptService } from "@bitwarden/vault";
@ -43,6 +45,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
viewingPasswordHistory = false;
viewOnly = false;
showPasswordCount = false;
cardIsExpired: boolean = false;
protected totpInterval: number;
protected override componentName = "app-vault-add-edit";
@ -115,6 +118,12 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
await this.totpTick(interval);
}, 1000);
}
const extensionRefreshEnabled = await firstValueFrom(
this.configService.getFeatureFlag$(FeatureFlag.ExtensionRefresh),
);
this.cardIsExpired = extensionRefreshEnabled && this.isCardExpiryInThePast();
}
ngOnDestroy() {
@ -226,6 +235,24 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
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() {
if (this.totpInterval) {
window.clearInterval(this.totpInterval);

View File

@ -194,6 +194,12 @@
"dr": {
"message": "Dr"
},
"cardExpiredTitle": {
"message": "Expired card"
},
"cardExpiredMessage": {
"message": "If you've renewed it, update the card's information"
},
"expirationMonth": {
"message": "Expiration month"
},

View File

@ -1,4 +1,8 @@
<ng-container *ngIf="!!cipher">
<bit-callout *ngIf="cardIsExpired" type="info" [title]="'cardExpiredTitle' | i18n">
{{ "cardExpiredMessage" | i18n }}
</bit-callout>
<!-- ITEM DETAILS -->
<app-item-details-v2
[cipher]="cipher"

View File

@ -8,10 +8,11 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
import { CollectionId } from "@bitwarden/common/types/guid";
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
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 { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { SearchModule } from "@bitwarden/components";
import { SearchModule, CalloutModule } from "@bitwarden/components";
import { AdditionalOptionsComponent } from "./additional-options/additional-options.component";
import { AttachmentsV2ViewComponent } from "./attachments/attachments-v2-view.component";
@ -28,6 +29,7 @@ import { ViewIdentitySectionsComponent } from "./view-identity-sections/view-ide
templateUrl: "cipher-view.component.html",
standalone: true,
imports: [
CalloutModule,
CommonModule,
SearchModule,
JslibModule,
@ -48,6 +50,7 @@ export class CipherViewComponent implements OnInit, OnDestroy {
folder$: Observable<FolderView>;
collections$: Observable<CollectionView[]>;
private destroyed$: Subject<void> = new Subject();
cardIsExpired: boolean = false;
constructor(
private organizationService: OrganizationService,
@ -57,6 +60,8 @@ export class CipherViewComponent implements OnInit, OnDestroy {
async ngOnInit() {
await this.loadCipherData();
this.cardIsExpired = this.isCardExpiryInThePast();
}
ngOnDestroy(): void {
@ -97,4 +102,24 @@ export class CipherViewComponent implements OnInit, OnDestroy {
.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;
}
}