1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-14 15:23:33 +00:00

Merge pull request #1995 from bitwarden/m-rpv1-force-reset

This commit is contained in:
Addison Beck
2021-08-11 15:50:15 -04:00
committed by GitHub
9 changed files with 216 additions and 9 deletions

View File

@@ -1752,5 +1752,14 @@
}, },
"emailVerificationRequiredDesc": { "emailVerificationRequiredDesc": {
"message": "You must verify your email to use this feature. You can verify your email in the web vault." "message": "You must verify your email to use this feature. You can verify your email in the web vault."
},
"updatedMasterPassword": {
"message": "Updated Master Password"
},
"updateMasterPassword": {
"message": "Update Master Password"
},
"updateMasterPasswordWarning": {
"message": "Your Master Password was recently changed by an administrator in your organization. In order to access the vault, you must update it now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour."
} }
} }

View File

@@ -10,6 +10,7 @@ import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.se
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from 'jslib-common/abstractions/state.service';
import { StorageService } from 'jslib-common/abstractions/storage.service'; import { StorageService } from 'jslib-common/abstractions/storage.service';
import { SyncService } from 'jslib-common/abstractions/sync.service'; import { SyncService } from 'jslib-common/abstractions/sync.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { LoginComponent as BaseLoginComponent } from 'jslib-angular/components/login.component'; import { LoginComponent as BaseLoginComponent } from 'jslib-angular/components/login.component';
@@ -23,10 +24,14 @@ export class LoginComponent extends BaseLoginComponent {
protected stateService: StateService, protected environmentService: EnvironmentService, protected stateService: StateService, protected environmentService: EnvironmentService,
protected passwordGenerationService: PasswordGenerationService, protected passwordGenerationService: PasswordGenerationService,
protected cryptoFunctionService: CryptoFunctionService, storageService: StorageService, protected cryptoFunctionService: CryptoFunctionService, storageService: StorageService,
syncService: SyncService) { syncService: SyncService, private userService: UserService) {
super(authService, router, platformUtilsService, i18nService, stateService, environmentService, passwordGenerationService, cryptoFunctionService, storageService); super(authService, router, platformUtilsService, i18nService, stateService, environmentService, passwordGenerationService, cryptoFunctionService, storageService);
super.onSuccessfulLogin = () => { super.onSuccessfulLogin = async () => {
return syncService.fullSync(true); await syncService.fullSync(true).then(async () => {
if (await this.userService.getForcePasswordReset()) {
this.router.navigate(['update-temp-password']);
}
});
}; };
super.successRoute = '/tabs/vault'; super.successRoute = '/tabs/vault';
} }

View File

@@ -31,6 +31,13 @@ export class SetPasswordComponent extends BaseSetPasswordComponent {
syncService: SyncService, route: ActivatedRoute) { syncService: SyncService, route: ActivatedRoute) {
super(i18nService, cryptoService, messagingService, userService, passwordGenerationService, super(i18nService, cryptoService, messagingService, userService, passwordGenerationService,
platformUtilsService, policyService, router, apiService, syncService, route); platformUtilsService, policyService, router, apiService, syncService, route);
super.onSuccessfulChangePassword = async () => {
if (await this.userService.getForcePasswordReset()) {
this.router.navigate(['update-temp-password']);
} else {
this.router.navigate([this.successRoute]);
}
};
} }
get masterPasswordScoreWidth() { get masterPasswordScoreWidth() {

View File

@@ -15,6 +15,7 @@ import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.se
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from 'jslib-common/abstractions/state.service';
import { StorageService } from 'jslib-common/abstractions/storage.service'; import { StorageService } from 'jslib-common/abstractions/storage.service';
import { SyncService } from 'jslib-common/abstractions/sync.service'; import { SyncService } from 'jslib-common/abstractions/sync.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { SsoComponent as BaseSsoComponent } from 'jslib-angular/components/sso.component'; import { SsoComponent as BaseSsoComponent } from 'jslib-angular/components/sso.component';
import { BrowserApi } from '../../browser/browserApi'; import { BrowserApi } from '../../browser/browserApi';
@@ -29,7 +30,7 @@ export class SsoComponent extends BaseSsoComponent {
storageService: StorageService, stateService: StateService, storageService: StorageService, stateService: StateService,
platformUtilsService: PlatformUtilsService, apiService: ApiService, platformUtilsService: PlatformUtilsService, apiService: ApiService,
cryptoFunctionService: CryptoFunctionService, passwordGenerationService: PasswordGenerationService, cryptoFunctionService: CryptoFunctionService, passwordGenerationService: PasswordGenerationService,
syncService: SyncService, environmentService: EnvironmentService) { syncService: SyncService, environmentService: EnvironmentService, private userService: UserService) {
super(authService, router, i18nService, route, storageService, stateService, platformUtilsService, super(authService, router, i18nService, route, storageService, stateService, platformUtilsService,
apiService, cryptoFunctionService, environmentService, passwordGenerationService); apiService, cryptoFunctionService, environmentService, passwordGenerationService);
@@ -44,5 +45,13 @@ export class SsoComponent extends BaseSsoComponent {
const thisWindow = window.open('', '_self'); const thisWindow = window.open('', '_self');
thisWindow.close(); thisWindow.close();
}; };
super.onSuccessfulLoginNavigate = async () => {
if (await this.userService.getForcePasswordReset()) {
this.router.navigate(['update-temp-password']);
} else {
this.router.navigate([this.successRoute]);
}
};
} }
} }

View File

@@ -20,6 +20,7 @@ import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.se
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from 'jslib-common/abstractions/state.service';
import { StorageService } from 'jslib-common/abstractions/storage.service'; import { StorageService } from 'jslib-common/abstractions/storage.service';
import { SyncService } from 'jslib-common/abstractions/sync.service'; import { SyncService } from 'jslib-common/abstractions/sync.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { BroadcasterService } from 'jslib-angular/services/broadcaster.service'; import { BroadcasterService } from 'jslib-angular/services/broadcaster.service';
@@ -44,11 +45,16 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
environmentService: EnvironmentService, private ngZone: NgZone, environmentService: EnvironmentService, private ngZone: NgZone,
private broadcasterService: BroadcasterService, private changeDetectorRef: ChangeDetectorRef, private broadcasterService: BroadcasterService, private changeDetectorRef: ChangeDetectorRef,
private popupUtilsService: PopupUtilsService, stateService: StateService, private popupUtilsService: PopupUtilsService, stateService: StateService,
storageService: StorageService, route: ActivatedRoute, private messagingService: MessagingService) { storageService: StorageService, route: ActivatedRoute, private messagingService: MessagingService,
private userService: UserService) {
super(authService, router, i18nService, apiService, platformUtilsService, window, environmentService, super(authService, router, i18nService, apiService, platformUtilsService, window, environmentService,
stateService, storageService, route); stateService, storageService, route);
super.onSuccessfulLogin = () => { super.onSuccessfulLogin = async () => {
return syncService.fullSync(true); return syncService.fullSync(true).then(async () => {
if (await this.userService.getForcePasswordReset()) {
this.router.navigate(['update-temp-password']);
};
});
}; };
super.successRoute = '/tabs/vault'; super.successRoute = '/tabs/vault';
this.webAuthnNewTab = this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari(); this.webAuthnNewTab = this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari();

View File

@@ -0,0 +1,100 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<header>
<div class="left">
<a (click)="logOut()">{{'logOut' | i18n}}</a>
</div>
<div class="center">
<span class="title">{{'updateMasterPassword' | i18n}}</span>
</div>
<div class="right">
<button type="submit" appBlurClick [disabled]="form.loading">
<span [hidden]="form.loading">{{'submit' | i18n}}</span>
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
</div>
</header>
<content>
<app-callout type="warning" title="{{'updateMasterPassword' | i18n}}">
{{'updateMasterPasswordWarning' | i18n}}
</app-callout>
<app-callout type="info" *ngIf="enforcedPolicyOptions">
{{'masterPasswordPolicyInEffect' | i18n}}
<ul>
<li *ngIf="enforcedPolicyOptions?.minComplexity > 0">
{{'policyInEffectMinComplexity' | i18n : getPasswordScoreAlertDisplay()}}
</li>
<li *ngIf="enforcedPolicyOptions?.minLength > 0">
{{'policyInEffectMinLength' | i18n : enforcedPolicyOptions?.minLength.toString()}}
</li>
<li *ngIf="enforcedPolicyOptions?.requireUpper">{{'policyInEffectUppercase' | i18n}}</li>
<li *ngIf="enforcedPolicyOptions?.requireLower">{{'policyInEffectLowercase' | i18n}}</li>
<li *ngIf="enforcedPolicyOptions?.requireNumbers">{{'policyInEffectNumbers' | i18n}}</li>
<li *ngIf="enforcedPolicyOptions?.requireSpecial">
{{'policyInEffectSpecial' | i18n : '!@#$%^&*'}}
</li>
</ul>
</app-callout>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">
{{'masterPass' | i18n}}
<strong class="sub-label text-{{masterPasswordScoreStyle.Color}}"
*ngIf="masterPasswordScoreStyle.Text">
{{masterPasswordScoreStyle.Text}}
</strong>
</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPassword" class="monospaced" [(ngModel)]="masterPassword" required
appInputVerbatim (input)="updatePasswordStrength()">
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(false)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
<div class="progress">
<div class="progress-bar bg-{{masterPasswordScoreStyle.Color}}" role="progressbar" aria-valuenow="0"
aria-valuemin="0" aria-valuemax="100" [ngStyle]="{width: (masterPasswordScoreStyle.Width + '%')}"
attr.aria-valuenow="{{masterPasswordScoreStyle.Width}}"></div>
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label>
<input id="masterPasswordRetype" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPasswordRetype" class="monospaced" [(ngModel)]="masterPasswordRetype" required
appInputVerbatim>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(true)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="hint">{{'masterPassHint' | i18n}}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint">
</div>
</div>
<div class="box-footer">
{{'masterPassHintDesc' | i18n}}
</div>
</div>
</content>
</form>

View File

@@ -0,0 +1,62 @@
import { Component } from '@angular/core';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from 'jslib-angular/components/update-temp-password.component';
interface MasterPasswordScore {
Color: string;
Text: string;
Width: number;
}
@Component({
selector: 'app-update-temp-password',
templateUrl: 'update-temp-password.component.html',
})
export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {
get masterPasswordScoreStyle(): MasterPasswordScore {
const scoreWidth = this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
switch (this.masterPasswordScore) {
case 4:
return {
Color: 'bg-success',
Text: 'strong',
Width: scoreWidth,
};
case 3:
return {
Color: 'bg-primary',
Text: 'good',
Width: scoreWidth,
};
case 2:
return {
Color: 'bg-warning',
Text: 'weak',
Width: scoreWidth,
};
default:
return {
Color: 'bg-danger',
Text: 'weak',
Width: scoreWidth,
};
}
}
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService, policyService: PolicyService,
cryptoService: CryptoService, userService: UserService,
messagingService: MessagingService, apiService: ApiService) {
super(i18nService, platformUtilsService, passwordGenerationService, policyService, cryptoService,
userService, messagingService, apiService);
}
}

View File

@@ -22,6 +22,7 @@ import { SetPasswordComponent } from './accounts/set-password.component';
import { SsoComponent } from './accounts/sso.component'; import { SsoComponent } from './accounts/sso.component';
import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component'; import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component';
import { TwoFactorComponent } from './accounts/two-factor.component'; import { TwoFactorComponent } from './accounts/two-factor.component';
import { UpdateTempPasswordComponent } from './accounts/update-temp-password.component';
import { PasswordGeneratorHistoryComponent } from './generator/password-generator-history.component'; import { PasswordGeneratorHistoryComponent } from './generator/password-generator-history.component';
import { PasswordGeneratorComponent } from './generator/password-generator.component'; import { PasswordGeneratorComponent } from './generator/password-generator.component';
@@ -261,6 +262,12 @@ const routes: Routes = [
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'edit-send' }, data: { state: 'edit-send' },
}, },
{
path: 'update-temp-password',
component: UpdateTempPasswordComponent,
canActivate: [AuthGuardService],
data: { state: 'update-temp-password' },
},
{ {
path: 'tabs', path: 'tabs',
component: TabsComponent, component: TabsComponent,

View File

@@ -20,6 +20,7 @@ import { SetPasswordComponent } from './accounts/set-password.component';
import { SsoComponent } from './accounts/sso.component'; import { SsoComponent } from './accounts/sso.component';
import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component'; import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component';
import { TwoFactorComponent } from './accounts/two-factor.component'; import { TwoFactorComponent } from './accounts/two-factor.component';
import { UpdateTempPasswordComponent } from './accounts/update-temp-password.component';
import { PasswordGeneratorHistoryComponent } from './generator/password-generator-history.component'; import { PasswordGeneratorHistoryComponent } from './generator/password-generator-history.component';
import { PasswordGeneratorComponent } from './generator/password-generator.component'; import { PasswordGeneratorComponent } from './generator/password-generator.component';
@@ -226,8 +227,10 @@ registerLocaleData(localeZhTw, 'zh-TW');
SendGroupingsComponent, SendGroupingsComponent,
SendListComponent, SendListComponent,
SendTypeComponent, SendTypeComponent,
SetPasswordComponent,
SettingsComponent, SettingsComponent,
ShareComponent, ShareComponent,
SsoComponent,
StopClickDirective, StopClickDirective,
StopPropDirective, StopPropDirective,
SyncComponent, SyncComponent,
@@ -235,9 +238,8 @@ registerLocaleData(localeZhTw, 'zh-TW');
TrueFalseValueDirective, TrueFalseValueDirective,
TwoFactorOptionsComponent, TwoFactorOptionsComponent,
TwoFactorComponent, TwoFactorComponent,
SsoComponent, UpdateTempPasswordComponent,
ViewComponent, ViewComponent,
SetPasswordComponent,
], ],
entryComponents: [], entryComponents: [],
providers: [ providers: [