diff --git a/src/app/accounts/login.component.html b/src/app/accounts/login.component.html index 142c6cc4..5800ce0c 100644 --- a/src/app/accounts/login.component.html +++ b/src/app/accounts/login.component.html @@ -33,6 +33,11 @@ {{'createAccount' | i18n}} +
+ + {{'enterpriseSingleSignOn' | i18n}} + +
{{'getMasterPasswordHint' | i18n}}
diff --git a/src/app/accounts/login.component.ts b/src/app/accounts/login.component.ts index 165260cf..efee5baa 100644 --- a/src/app/accounts/login.component.ts +++ b/src/app/accounts/login.component.ts @@ -4,12 +4,16 @@ import { ViewChild, ViewContainerRef, } from '@angular/core'; + import { Router } from '@angular/router'; import { EnvironmentComponent } from './environment.component'; import { AuthService } from 'jslib/abstractions/auth.service'; +import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service'; +import { EnvironmentService } from 'jslib/abstractions/environment.service'; import { I18nService } from 'jslib/abstractions/i18n.service'; +import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { StateService } from 'jslib/abstractions/state.service'; import { StorageService } from 'jslib/abstractions/storage.service'; @@ -35,7 +39,8 @@ export class LoginComponent extends BaseLoginComponent { platformUtilsService: PlatformUtilsService, stateService: StateService, environmentService: EnvironmentService, passwordGenerationService: PasswordGenerationService, cryptoFunctionService: CryptoFunctionService, storageService: StorageService) { - super(authService, router, platformUtilsService, i18nService, stateService, environmentService, passwordGenerationService, cryptoFunctionService, storageService); + super(authService, router, platformUtilsService, i18nService, stateService, environmentService, + passwordGenerationService, cryptoFunctionService, storageService); super.onSuccessfulLogin = () => { return syncService.fullSync(true); }; diff --git a/src/app/accounts/set-password.component.html b/src/app/accounts/set-password.component.html new file mode 100644 index 00000000..ccae85a8 --- /dev/null +++ b/src/app/accounts/set-password.component.html @@ -0,0 +1,105 @@ +
+
+ Bitwarden +

{{'setMasterPassword' | i18n}}

+
+ {{'ssoCompleteRegistration' | i18n}} + + {{'masterPasswordPolicyInEffect' | i18n}} +
    +
  • + {{'policyInEffectMinComplexity' | i18n : getPasswordScoreAlertDisplay()}} +
  • +
  • + {{'policyInEffectMinLength' | i18n : enforcedPolicyOptions?.minLength.toString()}} +
  • +
  • {{'policyInEffectUppercase' | i18n}}
  • +
  • {{'policyInEffectLowercase' | i18n}}
  • +
  • {{'policyInEffectNumbers' | i18n}}
  • +
  • {{'policyInEffectSpecial' | i18n : '!@#$%^&*'}} +
  • +
+
+
+ +
+
+
+
+
+ + +
+
+ + + +
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ + +
+
+ + + +
+
+
+
+
+
+
+
+ + +
+
+ +
+
+ + +
+ +
+ diff --git a/src/app/accounts/set-password.component.ts b/src/app/accounts/set-password.component.ts new file mode 100644 index 00000000..cd3e9e85 --- /dev/null +++ b/src/app/accounts/set-password.component.ts @@ -0,0 +1,68 @@ +import { Component } from '@angular/core'; + +import { + ActivatedRoute, + Router, +} from '@angular/router'; + +import { ApiService } from 'jslib/abstractions/api.service'; +import { CipherService } from 'jslib/abstractions/cipher.service'; +import { CryptoService } from 'jslib/abstractions/crypto.service'; +import { FolderService } from 'jslib/abstractions/folder.service'; +import { I18nService } from 'jslib/abstractions/i18n.service'; +import { MessagingService } from 'jslib/abstractions/messaging.service'; +import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service'; +import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; +import { PolicyService } from 'jslib/abstractions/policy.service'; +import { SyncService } from 'jslib/abstractions/sync.service'; +import { UserService } from 'jslib/abstractions/user.service'; + +import { + SetPasswordComponent as BaseSetPasswordComponent, +} from 'jslib/angular/components/set-password.component'; + +@Component({ + selector: 'app-set-password', + templateUrl: 'set-password.component.html', +}) +export class SetPasswordComponent extends BaseSetPasswordComponent { + constructor(apiService: ApiService, i18nService: I18nService, + cryptoService: CryptoService, messagingService: MessagingService, + userService: UserService, passwordGenerationService: PasswordGenerationService, + platformUtilsService: PlatformUtilsService, folderService: FolderService, + cipherService: CipherService, syncService: SyncService, + policyService: PolicyService, router: Router, route: ActivatedRoute) { + super(apiService, i18nService, cryptoService, messagingService, userService, passwordGenerationService, + platformUtilsService, folderService, cipherService, syncService, policyService, router, route); + } + + get masterPasswordScoreWidth() { + return this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20; + } + + get masterPasswordScoreColor() { + switch (this.masterPasswordScore) { + case 4: + return 'success'; + case 3: + return 'primary'; + case 2: + return 'warning'; + default: + return 'danger'; + } + } + + get masterPasswordScoreText() { + switch (this.masterPasswordScore) { + case 4: + return this.i18nService.t('strong'); + case 3: + return this.i18nService.t('good'); + case 2: + return this.i18nService.t('weak'); + default: + return this.masterPasswordScore != null ? this.i18nService.t('weak') : null; + } + } +} diff --git a/src/app/accounts/sso.component.html b/src/app/accounts/sso.component.html new file mode 100644 index 00000000..6b591fec --- /dev/null +++ b/src/app/accounts/sso.component.html @@ -0,0 +1,9 @@ +
+
+ Bitwarden +
+ + {{'loading' | i18n}} +
+
+
diff --git a/src/app/accounts/sso.component.ts b/src/app/accounts/sso.component.ts new file mode 100644 index 00000000..dd769627 --- /dev/null +++ b/src/app/accounts/sso.component.ts @@ -0,0 +1,35 @@ +import { Component } from '@angular/core'; + +import { + ActivatedRoute, + Router, +} from '@angular/router'; + +import { ApiService } from 'jslib/abstractions/api.service'; +import { AuthService } from 'jslib/abstractions/auth.service'; +import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service'; +import { I18nService } from 'jslib/abstractions/i18n.service'; +import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service'; +import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; +import { StateService } from 'jslib/abstractions/state.service'; +import { StorageService } from 'jslib/abstractions/storage.service'; + +import { SsoComponent as BaseSsoComponent } from 'jslib/angular/components/sso.component'; + +@Component({ + selector: 'app-sso', + templateUrl: 'sso.component.html', +}) +export class SsoComponent extends BaseSsoComponent { + constructor(authService: AuthService, router: Router, + i18nService: I18nService, route: ActivatedRoute, + storageService: StorageService, stateService: StateService, + platformUtilsService: PlatformUtilsService, apiService: ApiService, + cryptoFunctionService: CryptoFunctionService, + passwordGenerationService: PasswordGenerationService) { + super(authService, router, i18nService, route, storageService, stateService, platformUtilsService, + apiService, cryptoFunctionService, passwordGenerationService); + this.redirectUri = 'bitwarden://sso-callback'; + this.clientId = 'desktop'; + } +} diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 3045b7e7..8a22dcfd 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -10,6 +10,8 @@ import { HintComponent } from './accounts/hint.component'; import { LockComponent } from './accounts/lock.component'; import { LoginComponent } from './accounts/login.component'; import { RegisterComponent } from './accounts/register.component'; +import { SetPasswordComponent } from './accounts/set-password.component'; +import { SsoComponent } from './accounts/sso.component'; import { TwoFactorComponent } from './accounts/two-factor.component'; import { VaultComponent } from './vault/vault.component'; @@ -26,6 +28,8 @@ const routes: Routes = [ canActivate: [AuthGuardService], }, { path: 'hint', component: HintComponent }, + { path: 'set-password', component: SetPasswordComponent }, + { path: 'sso', component: SsoComponent }, ]; @NgModule({ diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 08f95b93..cc14a1c5 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -194,6 +194,8 @@ export class AppComponent implements OnInit { this.systemService.clearClipboard(message.clipboardValue, message.clearMs); } break; + case 'ssoCallback': + this.router.navigate(['sso'], { queryParams: { code: message.code, state: message.state } }); default: } }); diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 615fc0b5..b52d8ddc 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -23,7 +23,9 @@ import { LockComponent } from './accounts/lock.component'; import { LoginComponent } from './accounts/login.component'; import { PremiumComponent } from './accounts/premium.component'; import { RegisterComponent } from './accounts/register.component'; +import { SetPasswordComponent } from './accounts/set-password.component'; import { SettingsComponent } from './accounts/settings.component'; +import { SsoComponent } from './accounts/sso.component'; import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component'; import { TwoFactorComponent } from './accounts/two-factor.component'; @@ -176,8 +178,10 @@ registerLocaleData(localeZhTw, 'zh-TW'); RegisterComponent, SearchCiphersPipe, SelectCopyDirective, + SetPasswordComponent, SettingsComponent, ShareComponent, + SsoComponent, StopClickDirective, StopPropDirective, TrueFalseValueDirective, diff --git a/src/app/services.module.ts b/src/app/services.module.ts index df2e91fc..b0351cbf 100644 --- a/src/app/services.module.ts +++ b/src/app/services.module.ts @@ -210,6 +210,7 @@ export function initFactory(): Function { { provide: SystemServiceAbstraction, useValue: systemService }, { provide: EventServiceAbstraction, useValue: eventService }, { provide: PolicyServiceAbstraction, useValue: policyService }, + { provide: CryptoFunctionServiceAbstraction, useValue: cryptoFunctionService }, { provide: APP_INITIALIZER, useFactory: initFactory, diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json index 13d6164d..82c5636b 100644 --- a/src/locales/en/messages.json +++ b/src/locales/en/messages.json @@ -1344,5 +1344,59 @@ }, "vaultTimeoutLogOutConfirmationTitle": { "message": "Timeout Action Confirmation" + }, + "enterpriseSingleSignOn": { + "message": "Enterprise Single Sign-On" + }, + "setMasterPassword": { + "message": "Set Master Password" + }, + "ssoCompleteRegistration": { + "message": "In order to complete logging in with SSO, please set a master password to access and protect your vault." + }, + "newMasterPass": { + "message": "New Master Password" + }, + "confirmNewMasterPass": { + "message": "Confirm New Master Password" + }, + "masterPasswordPolicyInEffect": { + "message": "One or more organization policies require your master password to meet the following requirements:" + }, + "policyInEffectMinComplexity": { + "message": "Minimum complexity score of $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, + "policyInEffectMinLength": { + "message": "Minimum length of $LENGTH$", + "placeholders": { + "length": { + "content": "$1", + "example": "14" + } + } + }, + "policyInEffectUppercase": { + "message": "Contain one or more uppercase characters" + }, + "policyInEffectLowercase": { + "message": "Contain one or more lowercase characters" + }, + "policyInEffectNumbers": { + "message": "Contain one or more numbers" + }, + "policyInEffectSpecial": { + "message": "Contain one or more of the following special characters $CHARS$", + "placeholders": { + "chars": { + "content": "$1", + "example": "!@#$%^&*" + } + } } } diff --git a/src/main.ts b/src/main.ts index 88f3c09a..e582c4cf 100644 --- a/src/main.ts +++ b/src/main.ts @@ -89,7 +89,8 @@ export class Main { storageDefaults[ConstantsService.vaultTimeoutActionKey] = 'lock'; this.storageService = new ElectronStorageService(app.getPath('userData'), storageDefaults); - this.windowMain = new WindowMain(this.storageService, true); + this.windowMain = new WindowMain(this.storageService, true, undefined, undefined, + (arg) => this.processDeepLink(arg)); this.messagingMain = new MessagingMain(this, this.storageService); this.updaterMain = new UpdaterMain(this.i18nService, this.windowMain, 'desktop', () => { this.menuMain.updateMenuItem.enabled = false; @@ -138,11 +139,32 @@ export class Main { if (this.biometricMain != null) { await this.biometricMain.init(); } + + if (!app.isDefaultProtocolClient('bitwarden')) { + app.setAsDefaultProtocolClient('bitwarden'); + } + + // Process protocol for macOS + app.on('open-url', (event, url) => { + event.preventDefault(); + this.processDeepLink([url]); + }); }, (e: any) => { // tslint:disable-next-line console.error(e); }); } + + private processDeepLink(argv: string[]): void { + argv.filter((s) => s.indexOf('bitwarden://') === 0).forEach((s) => { + const url = new URL(s); + const code = url.searchParams.get('code'); + const receivedState = url.searchParams.get('state'); + if (code != null && receivedState != null) { + this.messagingService.send('ssoCallback', { code: code, state: receivedState }); + } + }); + } } const main = new Main(); diff --git a/src/scss/misc.scss b/src/scss/misc.scss index 06696705..7e780c0d 100644 --- a/src/scss/misc.scss +++ b/src/scss/misc.scss @@ -391,4 +391,9 @@ app-root > #loading { } } } + + ul { + padding-left: 40px; + margin: 0; + } } diff --git a/src/scss/pages.scss b/src/scss/pages.scss index bcda0725..89ace6f7 100644 --- a/src/scss/pages.scss +++ b/src/scss/pages.scss @@ -1,6 +1,6 @@ @import "variables.scss"; -#login-page, #lock-page { +#login-page, #lock-page, #sso-page, #set-password-page { display: flex; justify-content: center; align-items: center; @@ -123,6 +123,66 @@ } } +#sso-page { + .content { + width: 300px; + + .box { + margin-top: 30px; + margin-bottom: 30px; + text-align: center; + } + } +} + +#set-password-page { + .content { + width: 500px; + + p { + text-align: center + } + + p.lead, h1 { + font-size: $font-size-large; + text-align: center; + margin-bottom: 20px; + font-weight: normal; + } + + .buttons { + &:not(.with-rows), .buttons-row { + display: flex; + margin-bottom: 10px; + } + + &:not(.with-rows), .buttons-row:last-child { + margin-bottom: 20px; + } + + button { + margin-right: 10px; + + &:last-child { + margin-right: 0; + } + } + } + + .box { + margin-bottom: 15px; + + &.last { + margin-bottom: 20px; + } + } + + .box-content { + margin-bottom: 10px; + } + } +} + #register-page { .content { width: 400px;