diff --git a/jslib b/jslib index 14b01f2e..4fec6113 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit 14b01f2e5da839ffc312deb68e5b298d8fadb363 +Subproject commit 4fec6113144a96b57bab512fb1a1b1da72103b37 diff --git a/src/app/accounts/login.component.html b/src/app/accounts/login.component.html index 23cbd840..68971a6c 100644 --- a/src/app/accounts/login.component.html +++ b/src/app/accounts/login.component.html @@ -26,6 +26,9 @@ + + Enterprise Single Sign-On + diff --git a/src/app/accounts/sso.component.html b/src/app/accounts/sso.component.html new file mode 100644 index 00000000..6eac0959 --- /dev/null +++ b/src/app/accounts/sso.component.html @@ -0,0 +1,32 @@ +
+
+
+
+
+
+ + Logging in, please wait... +
+
+

+ Quickly log in using your organization's single sign-on portal. Please enter your + organization's identifier to begin.

+
+ + +
+ + + {{'cancel' | i18n}} + +
+
+
+
+
+
diff --git a/src/app/accounts/sso.component.ts b/src/app/accounts/sso.component.ts new file mode 100644 index 00000000..b9616506 --- /dev/null +++ b/src/app/accounts/sso.component.ts @@ -0,0 +1,41 @@ +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.successRoute = '/tabs/dashboard'; + this.redirectUri = 'bwdc://sso-callback'; + this.clientId = 'connector'; + } + + async submit() { + await super.submit(); + this.router.navigate(['login']); + } +} diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 946d4aaa..82fda652 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -8,6 +8,7 @@ import { AuthGuardService } from './services/auth-guard.service'; import { LaunchGuardService } from './services/launch-guard.service'; import { LoginComponent } from './accounts/login.component'; +import { SsoComponent } from './accounts/sso.component'; import { TwoFactorComponent } from './accounts/two-factor.component'; import { DashboardComponent } from './tabs/dashboard.component'; import { MoreComponent } from './tabs/more.component'; @@ -22,6 +23,7 @@ const routes: Routes = [ canActivate: [LaunchGuardService], }, { path: '2fa', component: TwoFactorComponent }, + { path: 'sso', component: SsoComponent }, { path: 'tabs', component: TabsComponent, diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 289f3c95..1a2a0214 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -133,6 +133,9 @@ export class AppComponent implements OnInit { properties: { label: message.label }, }); break; + case 'ssoCallback': + this.router.navigate(['sso'], { queryParams: { code: message.code, state: message.state } }); + break; default: } }); diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 3b68d5ff..5e5551dc 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -21,6 +21,7 @@ import { ModalComponent } from 'jslib/angular/components/modal.component'; import { EnvironmentComponent } from './accounts/environment.component'; import { LoginComponent } from './accounts/login.component'; +import { SsoComponent } from './accounts/sso.component'; import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component'; import { TwoFactorComponent } from './accounts/two-factor.component'; import { DashboardComponent } from './tabs/dashboard.component'; @@ -72,6 +73,7 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe'; MoreComponent, SearchCiphersPipe, SettingsComponent, + SsoComponent, StopClickDirective, StopPropDirective, TabsComponent, diff --git a/src/app/services/services.module.ts b/src/app/services/services.module.ts index df4c60ec..4cb71e28 100644 --- a/src/app/services/services.module.ts +++ b/src/app/services/services.module.ts @@ -62,7 +62,7 @@ const stateService = new StateService(); const broadcasterService = new BroadcasterService(); const messagingService = new ElectronRendererMessagingService(broadcasterService); const storageService: StorageServiceAbstraction = new ElectronStorageService(remote.app.getPath('userData')); -const platformUtilsService = new ElectronPlatformUtilsService(i18nService, messagingService, true, storageService); +const platformUtilsService = new ElectronPlatformUtilsService(i18nService, messagingService, false, storageService); const secureStorageService: StorageServiceAbstraction = new ElectronRendererSecureStorageService(); const cryptoFunctionService: CryptoFunctionServiceAbstraction = new NodeCryptoFunctionService(); const cryptoService = new CryptoService(storageService, secureStorageService, cryptoFunctionService); @@ -140,6 +140,7 @@ export function initFactory(): Function { { provide: ConfigurationService, useValue: configurationService }, { provide: SyncService, useValue: syncService }, { provide: PasswordGenerationServiceAbstraction, useValue: passwordGenerationService }, + { provide: CryptoFunctionServiceAbstraction, useValue: cryptoFunctionService }, { provide: APP_INITIALIZER, useFactory: initFactory, diff --git a/src/main.ts b/src/main.ts index cde9f0f3..561a92e5 100644 --- a/src/main.ts +++ b/src/main.ts @@ -52,7 +52,8 @@ export class Main { this.i18nService = new I18nService('en', './locales/'); this.storageService = new ElectronStorageService(app.getPath('userData')); - this.windowMain = new WindowMain(this.storageService, false, 800, 600); + this.windowMain = new WindowMain(this.storageService, false, 800, 600, + (arg) => this.processDeepLink(arg)); this.menuMain = new MenuMain(this); this.updaterMain = new UpdaterMain(this.i18nService, this.windowMain, 'directory-connector', () => { this.messagingService.send('checkingForUpdate'); @@ -78,11 +79,32 @@ export class Main { this.messagingMain.init(); await this.updaterMain.init(); await this.trayMain.init(this.i18nService.t('bitwardenDirectoryConnector')); + + if (!app.isDefaultProtocolClient('bwdc')) { + app.setAsDefaultProtocolClient('bwdc'); + } + + // 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('bwdc://') === 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();