1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 16:23:44 +00:00

[SG-416] Updates to Bitwarden Authenticator (#3045)

* [SG-416] Changed UI for TOTP codes on free plan and added link to get Premium. On browser, changed back action of premium.component in order to reuse on cipher details.

* [SSG-416] PR Fix

* [SSG-416] fix formatting

* [SSG-416] Updated desktop free plan OTP UI

* [SSG-416] noticed a bad div tag making file changes erratic

* [SG-416] fixed label

* [SSG-416] Fix formatting

* [SSG-416] Changed bootstrap classes to tailwind

* [SSG-416] Added premium and upgrade badge back. Muted placeholder totp code colors and button.

* [SSG-416] Change learn more to upgrade label on get premium modal. Fixed navigation for premium.

* [SSG-416] Removed unused image file.

* [SG-416] Changed browser "Premium subscription required" text to be all hyperlink.

* [SG-416] Fixed missing resource on browser

* [SG-416] Code format with lint
This commit is contained in:
André Filipe da Silva Bispo
2022-08-09 19:03:02 +01:00
committed by GitHub
parent 31cae4390f
commit c4f9c2cca6
13 changed files with 130 additions and 21 deletions

View File

@@ -1968,6 +1968,9 @@
"ssoKeyConnectorError": { "ssoKeyConnectorError": {
"message": "Key Connector error: make sure Key Connector is available and working correctly." "message": "Key Connector error: make sure Key Connector is available and working correctly."
}, },
"premiumSubcriptionRequired": {
"message": "Premium subscription required"
},
"organizationIsDisabled": { "organizationIsDisabled": {
"message": "Organization is disabled." "message": "Organization is disabled."
}, },

View File

@@ -1,6 +1,6 @@
<header> <header>
<div class="left"> <div class="left">
<button type="button" routerLink="/tabs/settings"> <button type="button" (click)="goBack()">
<span class="header-icon"><i class="bwi bwi-angle-left" aria-hidden="true"></i></span> <span class="header-icon"><i class="bwi bwi-angle-left" aria-hidden="true"></i></span>
<span>{{ "back" | i18n }}</span> <span>{{ "back" | i18n }}</span>
</button> </button>

View File

@@ -1,4 +1,4 @@
import { CurrencyPipe } from "@angular/common"; import { CurrencyPipe, Location } from "@angular/common";
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { PremiumComponent as BasePremiumComponent } from "@bitwarden/angular/components/premium.component"; import { PremiumComponent as BasePremiumComponent } from "@bitwarden/angular/components/premium.component";
@@ -21,6 +21,7 @@ export class PremiumComponent extends BasePremiumComponent {
apiService: ApiService, apiService: ApiService,
stateService: StateService, stateService: StateService,
logService: LogService, logService: LogService,
private location: Location,
private currencyPipe: CurrencyPipe private currencyPipe: CurrencyPipe
) { ) {
super(i18nService, platformUtilsService, apiService, logService, stateService); super(i18nService, platformUtilsService, apiService, logService, stateService);
@@ -32,4 +33,8 @@ export class PremiumComponent extends BasePremiumComponent {
this.priceString = this.priceString.replace("%price%", thePrice); this.priceString = this.priceString.replace("%price%", thePrice);
} }
} }
goBack() {
this.location.back();
}
} }

View File

@@ -139,7 +139,7 @@
<div <div
class="box-content-row box-content-row-flex totp" class="box-content-row box-content-row-flex totp"
[ngClass]="{ low: totpLow }" [ngClass]="{ low: totpLow }"
*ngIf="cipher.login.totp && totpCode" *ngIf="cipher.login.totp && totpCode && canAccessPremium"
> >
<div class="row-main"> <div class="row-main">
<span <span
@@ -177,6 +177,20 @@
</button> </button>
</div> </div>
</div> </div>
<div
class="box-content-row box-content-row-flex totp"
*ngIf="cipher.login.totp && !canAccessPremium"
>
<div class="row-main">
<span class="row-label">{{ "verificationCodeTotp" | i18n }}</span>
<span class="row-label">
<a routerLink="/premium">
{{ "premiumSubcriptionRequired" | i18n }}
</a>
</span>
</div>
</div>
</div> </div>
<!-- Card --> <!-- Card -->
<div *ngIf="cipher.card"> <div *ngIf="cipher.card">

View File

@@ -100,7 +100,7 @@
<div <div
class="box-content-row box-content-row-flex totp" class="box-content-row box-content-row-flex totp"
[ngClass]="{ low: totpLow }" [ngClass]="{ low: totpLow }"
*ngIf="cipher.login.totp && totpCode" *ngIf="cipher.login.totp && totpCode && canAccessPremium"
> >
<div class="row-main"> <div class="row-main">
<span <span
@@ -138,6 +138,19 @@
</button> </button>
</div> </div>
</div> </div>
<div
class="box-content-row box-content-row-flex totp"
*ngIf="cipher.login.totp && !canAccessPremium"
>
<div class="row-main">
<span class="row-label">{{ "verificationCodeTotp" | i18n }}</span>
<span class="row-label">
<a [routerLink]="" (click)="showGetPremium()"
>{{ "premiumSubcriptionRequired" | i18n }}
</a>
</span>
</div>
</div>
</div> </div>
<!-- Card --> <!-- Card -->
<div *ngIf="cipher.card"> <div *ngIf="cipher.card">

View File

@@ -115,4 +115,10 @@ export class ViewComponent extends BaseViewComponent implements OnChanges {
}); });
} }
} }
showGetPremium() {
if (!this.canAccessPremium) {
this.messagingService.send("premiumRequired");
}
}
} }

View File

@@ -1979,6 +1979,9 @@
"apiKey": { "apiKey": {
"message": "API Key" "message": "API Key"
}, },
"premiumSubcriptionRequired": {
"message": "Premium subscription required"
},
"organizationIsDisabled": { "organizationIsDisabled": {
"message": "Organization is disabled." "message": "Organization is disabled."
}, },

View File

@@ -147,7 +147,7 @@ export class AppComponent implements OnDestroy, OnInit {
const premiumConfirmed = await this.platformUtilsService.showDialog( const premiumConfirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("premiumRequiredDesc"), this.i18nService.t("premiumRequiredDesc"),
this.i18nService.t("premiumRequired"), this.i18nService.t("premiumRequired"),
this.i18nService.t("learnMore"), this.i18nService.t("upgrade"),
this.i18nService.t("cancel") this.i18nService.t("cancel")
); );
if (premiumConfirmed) { if (premiumConfirmed) {

View File

@@ -166,8 +166,8 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row"> <div class="tw-flex tw-flex-row">
<div class="col-6 form-group"> <div class="tw-mb-4 tw-w-1/2">
<label for="loginTotp">{{ "authenticatorKeyTotp" | i18n }}</label> <label for="loginTotp">{{ "authenticatorKeyTotp" | i18n }}</label>
<input <input
id="loginTotp" id="loginTotp"
@@ -179,14 +179,41 @@
[disabled]="cipher.isDeleted || !cipher.viewPassword || viewOnly" [disabled]="cipher.isDeleted || !cipher.viewPassword || viewOnly"
/> />
</div> </div>
<div class="col-6 form-group totp d-flex align-items-end" [ngClass]="{ low: totpLow }"> <div class="tw-mb-4 tw-ml-4 tw-flex tw-w-1/2 tw-items-end" [ngClass]="{ low: totpLow }">
<div *ngIf="!cipher.login.totp || !totpCode"> <div
<img class="totp tw-flex tw-flex-row tw-items-center"
src="../../images/totp-countdown.png" *ngIf="!cipher.login.totp || (cipher.login.totp && !canAccessPremium)"
id="totpImage" >
<span class="totp-countdown">
<span class="totp-sec tw-text-muted">15</span>
<svg>
<g>
<circle
class="totp-circle-muted inner"
r="12.6"
cy="16"
cx="16"
opacity="0.25"
[ngStyle]="{ 'stroke-dashoffset.px': 40 }"
></circle>
<circle
class="totp-circle-muted outer"
opacity="0.25"
r="14"
cy="16"
cx="16"
></circle>
</g>
</svg>
</span>
<span
class="totp-code tw-mr-3 tw-ml-2 tw-text-muted"
title="{{ 'verificationCodeTotp' | i18n }}" title="{{ 'verificationCodeTotp' | i18n }}"
class="ml-2" >--- ---</span
/> >
<i class="bwi bwi-lg bwi-clone tw-text-muted" aria-hidden="true"></i>
</div>
<div class="tw-pb-2" *ngIf="!cipher.login.totp || !totpCode">
<app-premium-badge <app-premium-badge
*ngIf="!organization && !cipher.organizationId" *ngIf="!organization && !cipher.organizationId"
class="ml-3" class="ml-3"
@@ -209,8 +236,11 @@
{{ "upgrade" | i18n }} {{ "upgrade" | i18n }}
</a> </a>
</div> </div>
<div *ngIf="cipher.login.totp && totpCode" class="d-flex align-items-center"> <div
<span class="totp-countdown mr-3 ml-2"> *ngIf="cipher.login.totp && totpCode && canAccessPremium"
class="totp tw-flex tw-flex-row tw-items-center"
>
<span class="totp-countdown">
<span class="totp-sec">{{ totpSec }}</span> <span class="totp-sec">{{ totpSec }}</span>
<svg> <svg>
<g> <g>
@@ -225,16 +255,18 @@
</g> </g>
</svg> </svg>
</span> </span>
<span class="totp-code mr-2" title="{{ 'verificationCodeTotp' | i18n }}">{{ <span
totpCodeFormatted class="totp-code tw-mr-2 tw-ml-2 tw-mt-1"
}}</span> title="{{ 'verificationCodeTotp' | i18n }}"
>{{ totpCodeFormatted }}</span
>
<button <button
type="button" type="button"
class="btn btn-link" class="tw-items-center tw-border-none tw-bg-transparent tw-text-primary-500"
appA11yTitle="{{ 'copyVerificationCode' | i18n }}" appA11yTitle="{{ 'copyVerificationCode' | i18n }}"
(click)="copy(totpCode, 'verificationCodeTotp', 'TOTP')" (click)="copy(totpCode, 'verificationCodeTotp', 'TOTP')"
> >
<i class="bwi bwi-clone" aria-hidden="true"></i> <i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button> </button>
</div> </div>
</div> </div>

View File

@@ -152,6 +152,17 @@ export class AddEditComponent extends BaseAddEditComponent {
}); });
} }
showGetPremium() {
if (this.canAccessPremium) {
return;
}
if (this.cipher.organizationUseTotp) {
this.upgradeOrganization();
} else {
this.premiumRequired();
}
}
viewHistory() { viewHistory() {
this.viewingPasswordHistory = !this.viewingPasswordHistory; this.viewingPasswordHistory = !this.viewingPasswordHistory;
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 950 B

View File

@@ -5220,6 +5220,9 @@
} }
} }
}, },
"premiumSubcriptionRequired": {
"message": "Premium subscription required"
},
"scim": { "scim": {
"message": "SCIM Provisioning", "message": "SCIM Provisioning",
"description": "The text, 'SCIM', is an acronymn and should not be translated." "description": "The text, 'SCIM', is an acronymn and should not be translated."

View File

@@ -130,6 +130,25 @@
stroke-width: 2; stroke-width: 2;
} }
} }
.totp-circle-muted {
fill: none;
@include themify($themes) {
stroke: themed("info");
}
&.inner {
stroke-dasharray: 78.6;
stroke-dashoffset: 0;
stroke-width: 3;
}
&.outer {
stroke-dasharray: 88;
stroke-dashoffset: 0;
stroke-width: 2;
}
}
} }
> .align-items-center { > .align-items-center {