mirror of
https://github.com/bitwarden/directory-connector
synced 2025-12-05 23:53:21 +00:00
Use organization api key for auth (#121)
* Use api key for login * Remove user login and organization setting * Override Api authentication to expect organization keys * Linter fixes * Use public API The organization api key is valid only in the public api scope * Use organization api key in CLI utility * Serialize storageService writes * Prefer multiple awaits to .then chains * Initial PR review * Do not treat api key inputs as passwords This conforms with how they are handled in CLI/web * Update jslib * PR feedback
This commit is contained in:
2
jslib
2
jslib
Submodule jslib updated: 25917faf91...ca61e13b57
@@ -8,14 +8,15 @@
|
|||||||
<h5 class="card-header">{{'logIn' | i18n}}</h5>
|
<h5 class="card-header">{{'logIn' | i18n}}</h5>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="email">{{'emailAddress' | i18n}}</label>
|
<label for="client_id">{{'clientId' | i18n}}</label>
|
||||||
<input id="email" type="text" name="Email" [(ngModel)]="email" class="form-control">
|
<input id="client_id" name="ClientId" [(ngModel)]="clientId"
|
||||||
|
class="form-control">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="row-main">
|
<div class="row-main">
|
||||||
<label for="masterPassword">{{'masterPass' | i18n}}</label>
|
<label for="client_secret">{{'clientSecret' | i18n}}</label>
|
||||||
<input id="masterPassword" type="password" name="MasterPassword"
|
<input id="client_secret" name="ClientSecret"
|
||||||
[(ngModel)]="masterPassword" class="form-control">
|
[(ngModel)]="clientSecret" class="form-control">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
@@ -25,10 +26,6 @@
|
|||||||
<i class="fa fa-sign-in fa-fw" [hidden]="form.loading"></i>
|
<i class="fa fa-sign-in fa-fw" [hidden]="form.loading"></i>
|
||||||
{{'logIn' | i18n}}
|
{{'logIn' | i18n}}
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-secondary ml-1" (click)="sso()">
|
|
||||||
<i class="fa fa-bank" aria-hidden="true"></i>
|
|
||||||
{{'enterpriseSingleSignOn' | i18n}}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-link ml-auto" (click)="settings()">
|
<button type="button" class="btn btn-link ml-auto" (click)="settings()">
|
||||||
{{'settings' | i18n}}
|
{{'settings' | i18n}}
|
||||||
80
src/app/accounts/apiKey.component.ts
Normal file
80
src/app/accounts/apiKey.component.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import {
|
||||||
|
Component,
|
||||||
|
ComponentFactoryResolver,
|
||||||
|
Input,
|
||||||
|
ViewChild,
|
||||||
|
ViewContainerRef,
|
||||||
|
} from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { EnvironmentComponent } from './environment.component';
|
||||||
|
|
||||||
|
import { ApiKeyService } from 'jslib/abstractions/apiKey.service';
|
||||||
|
import { AuthService } from 'jslib/abstractions/auth.service';
|
||||||
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
|
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||||
|
|
||||||
|
import { ModalComponent } from 'jslib/angular/components/modal.component';
|
||||||
|
import { Utils } from 'jslib/misc/utils';
|
||||||
|
import { ConfigurationService } from '../../services/configuration.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-apiKey',
|
||||||
|
templateUrl: 'apiKey.component.html',
|
||||||
|
})
|
||||||
|
export class ApiKeyComponent {
|
||||||
|
@ViewChild('environment', { read: ViewContainerRef, static: true }) environmentModal: ViewContainerRef;
|
||||||
|
@Input() clientId: string = '';
|
||||||
|
@Input() clientSecret: string = '';
|
||||||
|
|
||||||
|
formPromise: Promise<any>;
|
||||||
|
successRoute = '/tabs/dashboard';
|
||||||
|
|
||||||
|
constructor(private authService: AuthService, private apiKeyService: ApiKeyService, private router: Router,
|
||||||
|
private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
|
||||||
|
private configurationService: ConfigurationService, private platformUtilsService: PlatformUtilsService) { }
|
||||||
|
|
||||||
|
async submit() {
|
||||||
|
if (this.clientId == null || this.clientId === '') {
|
||||||
|
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||||
|
this.i18nService.t('clientIdRequired'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.clientId.startsWith('organization')) {
|
||||||
|
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||||
|
this.i18nService.t('orgApiKeyRequired'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.clientSecret == null || this.clientSecret === '') {
|
||||||
|
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||||
|
this.i18nService.t('clientSecretRequired'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const idParts = this.clientId.split('.');
|
||||||
|
|
||||||
|
if (idParts.length !== 2 || idParts[0] !== 'organization' || !Utils.isGuid(idParts[1])) {
|
||||||
|
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||||
|
this.i18nService.t('invalidClientId'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.formPromise = this.authService.logInApiKey(this.clientId, this.clientSecret);
|
||||||
|
await this.formPromise;
|
||||||
|
const organizationId = await this.apiKeyService.getEntityId();
|
||||||
|
await this.configurationService.saveOrganizationId(organizationId);
|
||||||
|
this.router.navigate([this.successRoute]);
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
settings() {
|
||||||
|
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
||||||
|
const modal = this.environmentModal.createComponent(factory).instance;
|
||||||
|
const childComponent = modal.show<EnvironmentComponent>(EnvironmentComponent,
|
||||||
|
this.environmentModal);
|
||||||
|
|
||||||
|
childComponent.onSaved.subscribe(() => {
|
||||||
|
modal.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
import {
|
|
||||||
Component,
|
|
||||||
ComponentFactoryResolver,
|
|
||||||
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';
|
|
||||||
|
|
||||||
import { LoginComponent as BaseLoginComponent } from 'jslib/angular/components/login.component';
|
|
||||||
import { ModalComponent } from 'jslib/angular/components/modal.component';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-login',
|
|
||||||
templateUrl: 'login.component.html',
|
|
||||||
})
|
|
||||||
export class LoginComponent extends BaseLoginComponent {
|
|
||||||
@ViewChild('environment', { read: ViewContainerRef, static: true }) environmentModal: ViewContainerRef;
|
|
||||||
|
|
||||||
constructor(authService: AuthService, router: Router,
|
|
||||||
i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
|
|
||||||
storageService: StorageService, stateService: StateService,
|
|
||||||
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
|
|
||||||
passwordGenerationService: PasswordGenerationService, cryptoFunctionService: CryptoFunctionService) {
|
|
||||||
super(authService, router,
|
|
||||||
platformUtilsService, i18nService,
|
|
||||||
stateService, environmentService,
|
|
||||||
passwordGenerationService, cryptoFunctionService,
|
|
||||||
storageService);
|
|
||||||
super.successRoute = '/tabs/dashboard';
|
|
||||||
}
|
|
||||||
|
|
||||||
settings() {
|
|
||||||
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
|
||||||
const modal = this.environmentModal.createComponent(factory).instance;
|
|
||||||
const childComponent = modal.show<EnvironmentComponent>(EnvironmentComponent,
|
|
||||||
this.environmentModal);
|
|
||||||
|
|
||||||
childComponent.onSaved.subscribe(() => {
|
|
||||||
modal.close();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
sso() {
|
|
||||||
return super.launchSsoBrowser('connector', 'bwdc://sso-callback');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
<div class="container-fluid">
|
|
||||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
|
||||||
<div class="row justify-content-center">
|
|
||||||
<div class="col-md-8 col-lg-6">
|
|
||||||
<div class="card" *ngIf="!showMasterPassRedirect">
|
|
||||||
<div class="card-body">
|
|
||||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
|
||||||
{{'loading' | i18n}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card" *ngIf="showMasterPassRedirect">
|
|
||||||
<h5 class="card-header">{{'setMasterPassword' | i18n}}</h5>
|
|
||||||
<div class="card-body">
|
|
||||||
<p class="text-center">{{'setMasterPasswordRedirect' | i18n}}</p>
|
|
||||||
<hr>
|
|
||||||
<div class="d-flex">
|
|
||||||
<button type="button" class="btn btn-primary btn-block btn-submit"
|
|
||||||
(click)="launchWebVault()">
|
|
||||||
{{'launchWebVault' | i18n}}
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-secondary btn-block ml-2 mt-0" (click)="logOut()">
|
|
||||||
{{'logOut' | i18n}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
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 { EnvironmentService } from 'jslib/abstractions/environment.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 { 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 {
|
|
||||||
showMasterPassRedirect: boolean = false;
|
|
||||||
|
|
||||||
constructor(authService: AuthService, router: Router,
|
|
||||||
i18nService: I18nService, route: ActivatedRoute,
|
|
||||||
storageService: StorageService, stateService: StateService,
|
|
||||||
platformUtilsService: PlatformUtilsService, apiService: ApiService,
|
|
||||||
cryptoFunctionService: CryptoFunctionService,
|
|
||||||
passwordGenerationService: PasswordGenerationService, private messagingService: MessagingService,
|
|
||||||
private environmentService: EnvironmentService) {
|
|
||||||
super(authService, router, i18nService, route, storageService, stateService, platformUtilsService,
|
|
||||||
apiService, cryptoFunctionService, passwordGenerationService);
|
|
||||||
this.successRoute = '/tabs/dashboard';
|
|
||||||
this.redirectUri = 'bwdc://sso-callback';
|
|
||||||
this.clientId = 'connector';
|
|
||||||
this.onSuccessfulLoginChangePasswordNavigate = this.redirectSetMasterPass;
|
|
||||||
}
|
|
||||||
|
|
||||||
async redirectSetMasterPass() {
|
|
||||||
this.showMasterPassRedirect = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
launchWebVault() {
|
|
||||||
const webUrl = this.environmentService.webVaultUrl == null ? 'https://vault.bitwarden.com' :
|
|
||||||
this.environmentService.webVaultUrl;
|
|
||||||
|
|
||||||
this.platformUtilsService.launchUri(webUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
async logOut() {
|
|
||||||
const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('logOutConfirmation'),
|
|
||||||
this.i18nService.t('logOut'), this.i18nService.t('logOut'), this.i18nService.t('cancel'));
|
|
||||||
if (confirmed) {
|
|
||||||
this.messagingService.send('logout');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
<div class="modal fade">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h3 class="modal-title">{{'twoStepOptions' | i18n}}</h3>
|
|
||||||
<button type="button" class="close" data-dismiss="modal" title="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p *ngFor="let p of providers">
|
|
||||||
<a href="#" appStopClick (click)="choose(p)">
|
|
||||||
<strong>{{p.name}}</strong>
|
|
||||||
</a>
|
|
||||||
<br /> {{p.description}}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<a href="#" (click)="recover()">
|
|
||||||
<strong>{{'recoveryCodeTitle' | i18n}}</strong>
|
|
||||||
</a>
|
|
||||||
<br /> {{'recoveryCodeDesc' | i18n}}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
|
|
||||||
import { AuthService } from 'jslib/abstractions/auth.service';
|
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
|
||||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
|
||||||
|
|
||||||
import {
|
|
||||||
TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent,
|
|
||||||
} from 'jslib/angular/components/two-factor-options.component';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-two-factor-options',
|
|
||||||
templateUrl: 'two-factor-options.component.html',
|
|
||||||
})
|
|
||||||
export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent {
|
|
||||||
constructor(authService: AuthService, router: Router,
|
|
||||||
i18nService: I18nService, platformUtilsService: PlatformUtilsService) {
|
|
||||||
super(authService, router, i18nService, platformUtilsService, window);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
<div class="container-fluid">
|
|
||||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
|
||||||
<div class="row justify-content-center">
|
|
||||||
<div class="col-md-8 col-lg-6">
|
|
||||||
<div class="card">
|
|
||||||
<h5 class="card-header">{{title}}</h5>
|
|
||||||
<div class="card-body">
|
|
||||||
<ng-container
|
|
||||||
*ngIf="selectedProviderType === providerType.Email || selectedProviderType === providerType.Authenticator">
|
|
||||||
<p *ngIf="selectedProviderType === providerType.Authenticator">
|
|
||||||
{{'enterVerificationCodeApp' | i18n}}
|
|
||||||
</p>
|
|
||||||
<p *ngIf="selectedProviderType === providerType.Email">
|
|
||||||
{{'enterVerificationCodeEmail' | i18n : twoFactorEmail}}
|
|
||||||
</p>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="code">{{'verificationCode' | i18n}}</label>
|
|
||||||
<input id="code" type="text" name="Code" [(ngModel)]="token" appAutofocus
|
|
||||||
class="form-control">
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="selectedProviderType === providerType.Yubikey">
|
|
||||||
<p>{{'insertYubiKey' | i18n}}</p>
|
|
||||||
<p><img src="../../images/yubikey.jpg" class="img-fluid rounded" alt=""></p>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="code">{{'verificationCode' | i18n}}</label>
|
|
||||||
<input id="code" type="password" name="Code" [(ngModel)]="token" appAutofocus
|
|
||||||
class="form-control">
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="selectedProviderType === providerType.Duo ||
|
|
||||||
selectedProviderType === providerType.OrganizationDuo">
|
|
||||||
<div id="duo-frame">
|
|
||||||
<iframe id="duo_iframe"></iframe>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
<div class="form-group"
|
|
||||||
*ngIf="selectedProviderType != null && selectedProviderType !== providerType.U2f">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" id="remember" [(ngModel)]="remember"
|
|
||||||
name="Remember">
|
|
||||||
<label class="form-check-label" for="remember">{{'rememberMe' | i18n}}</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ng-container class="card-body"
|
|
||||||
*ngIf="selectedProviderType === null || selectedProviderType === providerType.U2f">
|
|
||||||
<p>{{'noTwoStepProviders' | i18n}}</p>
|
|
||||||
<p>{{'noTwoStepProviders2' | i18n}}</p>
|
|
||||||
</ng-container>
|
|
||||||
<button type="submit" class="btn btn-primary" [disabled]="form.loading" *ngIf="selectedProviderType != null && selectedProviderType !== providerType.U2f && selectedProviderType !== providerType.Duo &&
|
|
||||||
selectedProviderType !== providerType.OrganizationDuo">
|
|
||||||
<i class="fa fa-sign-in fa-fw" [hidden]="form.loading"></i>
|
|
||||||
<i class="fa fa-spinner fa-fw fa-spin" [hidden]="!form.loading"></i>
|
|
||||||
{{'continue' | i18n}}
|
|
||||||
</button>
|
|
||||||
<a routerLink="/login" class="btn btn-link">{{'cancel' | i18n}}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-center mt-3">
|
|
||||||
<a href="#" appStopClick (click)="anotherMethod()">{{'useAnotherTwoStepMethod' | i18n}}</a>
|
|
||||||
<a href="#" appStopClick (click)="sendEmail(true)" [appApiAction]="emailPromise"
|
|
||||||
*ngIf="selectedProviderType === providerType.Email">
|
|
||||||
{{'sendVerificationCodeEmailAgain' | i18n}}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<ng-template #twoFactorOptions></ng-template>
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
import {
|
|
||||||
Component,
|
|
||||||
ComponentFactoryResolver,
|
|
||||||
ViewChild,
|
|
||||||
ViewContainerRef,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ActivatedRoute,
|
|
||||||
Router,
|
|
||||||
} from '@angular/router';
|
|
||||||
|
|
||||||
import { TwoFactorOptionsComponent } from './two-factor-options.component';
|
|
||||||
|
|
||||||
import { TwoFactorProviderType } from 'jslib/enums/twoFactorProviderType';
|
|
||||||
|
|
||||||
import { ApiService } from 'jslib/abstractions/api.service';
|
|
||||||
import { AuthService } from 'jslib/abstractions/auth.service';
|
|
||||||
import { EnvironmentService } from 'jslib/abstractions/environment.service';
|
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
|
||||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
|
||||||
import { StateService } from 'jslib/abstractions/state.service';
|
|
||||||
import { StorageService } from 'jslib/abstractions/storage.service';
|
|
||||||
|
|
||||||
import { ModalComponent } from 'jslib/angular/components/modal.component';
|
|
||||||
import { TwoFactorComponent as BaseTwoFactorComponent } from 'jslib/angular/components/two-factor.component';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-two-factor',
|
|
||||||
templateUrl: 'two-factor.component.html',
|
|
||||||
})
|
|
||||||
export class TwoFactorComponent extends BaseTwoFactorComponent {
|
|
||||||
@ViewChild('twoFactorOptions', { read: ViewContainerRef, static: true }) twoFactorOptionsModal: ViewContainerRef;
|
|
||||||
|
|
||||||
constructor(authService: AuthService, router: Router,
|
|
||||||
i18nService: I18nService, apiService: ApiService,
|
|
||||||
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
|
|
||||||
private componentFactoryResolver: ComponentFactoryResolver, stateService: StateService,
|
|
||||||
storageService: StorageService, route: ActivatedRoute) {
|
|
||||||
super(authService, router, i18nService, apiService, platformUtilsService, window, environmentService,
|
|
||||||
stateService, storageService, route);
|
|
||||||
}
|
|
||||||
|
|
||||||
async ngOnInit() {
|
|
||||||
await super.ngOnInit();
|
|
||||||
super.successRoute = '/tabs/dashboard';
|
|
||||||
}
|
|
||||||
|
|
||||||
anotherMethod() {
|
|
||||||
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
|
||||||
const modal = this.twoFactorOptionsModal.createComponent(factory).instance;
|
|
||||||
const childComponent = modal.show<TwoFactorOptionsComponent>(TwoFactorOptionsComponent,
|
|
||||||
this.twoFactorOptionsModal);
|
|
||||||
|
|
||||||
childComponent.onProviderSelected.subscribe(async (provider: TwoFactorProviderType) => {
|
|
||||||
modal.close();
|
|
||||||
this.selectedProviderType = provider;
|
|
||||||
await this.init();
|
|
||||||
});
|
|
||||||
childComponent.onRecoverSelected.subscribe(() => {
|
|
||||||
modal.close();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,9 +7,7 @@ import {
|
|||||||
import { AuthGuardService } from './services/auth-guard.service';
|
import { AuthGuardService } from './services/auth-guard.service';
|
||||||
import { LaunchGuardService } from './services/launch-guard.service';
|
import { LaunchGuardService } from './services/launch-guard.service';
|
||||||
|
|
||||||
import { LoginComponent } from './accounts/login.component';
|
import { ApiKeyComponent } from './accounts/apiKey.component';
|
||||||
import { SsoComponent } from './accounts/sso.component';
|
|
||||||
import { TwoFactorComponent } from './accounts/two-factor.component';
|
|
||||||
import { DashboardComponent } from './tabs/dashboard.component';
|
import { DashboardComponent } from './tabs/dashboard.component';
|
||||||
import { MoreComponent } from './tabs/more.component';
|
import { MoreComponent } from './tabs/more.component';
|
||||||
import { SettingsComponent } from './tabs/settings.component';
|
import { SettingsComponent } from './tabs/settings.component';
|
||||||
@@ -19,11 +17,9 @@ const routes: Routes = [
|
|||||||
{ path: '', redirectTo: '/login', pathMatch: 'full' },
|
{ path: '', redirectTo: '/login', pathMatch: 'full' },
|
||||||
{
|
{
|
||||||
path: 'login',
|
path: 'login',
|
||||||
component: LoginComponent,
|
component: ApiKeyComponent,
|
||||||
canActivate: [LaunchGuardService],
|
canActivate: [LaunchGuardService],
|
||||||
},
|
},
|
||||||
{ path: '2fa', component: TwoFactorComponent },
|
|
||||||
{ path: 'sso', component: SsoComponent },
|
|
||||||
{
|
{
|
||||||
path: 'tabs',
|
path: 'tabs',
|
||||||
component: TabsComponent,
|
component: TabsComponent,
|
||||||
|
|||||||
@@ -73,12 +73,6 @@ export class AppComponent implements OnInit {
|
|||||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
|
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
|
||||||
this.ngZone.run(async () => {
|
this.ngZone.run(async () => {
|
||||||
switch (message.command) {
|
switch (message.command) {
|
||||||
case 'loggedIn':
|
|
||||||
if (await this.userService.isAuthenticated()) {
|
|
||||||
const profile = await this.apiService.getProfile();
|
|
||||||
this.stateService.save('profileOrganizations', profile.organizations);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'syncScheduleStarted':
|
case 'syncScheduleStarted':
|
||||||
case 'syncScheduleStopped':
|
case 'syncScheduleStopped':
|
||||||
this.stateService.save('syncingDir', message.command === 'syncScheduleStarted');
|
this.stateService.save('syncingDir', message.command === 'syncScheduleStarted');
|
||||||
@@ -140,10 +134,8 @@ export class AppComponent implements OnInit {
|
|||||||
private async logOut(expired: boolean) {
|
private async logOut(expired: boolean) {
|
||||||
const userId = await this.userService.getUserId();
|
const userId = await this.userService.getUserId();
|
||||||
|
|
||||||
await Promise.all([
|
await this.tokenService.clearToken();
|
||||||
this.tokenService.clearToken(),
|
await this.userService.clear();
|
||||||
this.userService.clear(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
this.authService.logOut(async () => {
|
this.authService.logOut(async () => {
|
||||||
if (expired) {
|
if (expired) {
|
||||||
|
|||||||
@@ -17,11 +17,8 @@ import { CalloutComponent } from 'jslib/angular/components/callout.component';
|
|||||||
import { IconComponent } from 'jslib/angular/components/icon.component';
|
import { IconComponent } from 'jslib/angular/components/icon.component';
|
||||||
import { ModalComponent } from 'jslib/angular/components/modal.component';
|
import { ModalComponent } from 'jslib/angular/components/modal.component';
|
||||||
|
|
||||||
|
import { ApiKeyComponent } from './accounts/apiKey.component';
|
||||||
import { EnvironmentComponent } from './accounts/environment.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';
|
import { DashboardComponent } from './tabs/dashboard.component';
|
||||||
import { MoreComponent } from './tabs/more.component';
|
import { MoreComponent } from './tabs/more.component';
|
||||||
import { SettingsComponent } from './tabs/settings.component';
|
import { SettingsComponent } from './tabs/settings.component';
|
||||||
@@ -51,6 +48,7 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
|
|||||||
declarations: [
|
declarations: [
|
||||||
A11yTitleDirective,
|
A11yTitleDirective,
|
||||||
ApiActionDirective,
|
ApiActionDirective,
|
||||||
|
ApiKeyComponent,
|
||||||
AppComponent,
|
AppComponent,
|
||||||
AutofocusDirective,
|
AutofocusDirective,
|
||||||
BlurClickDirective,
|
BlurClickDirective,
|
||||||
@@ -61,22 +59,17 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
|
|||||||
FallbackSrcDirective,
|
FallbackSrcDirective,
|
||||||
I18nPipe,
|
I18nPipe,
|
||||||
IconComponent,
|
IconComponent,
|
||||||
LoginComponent,
|
|
||||||
ModalComponent,
|
ModalComponent,
|
||||||
MoreComponent,
|
MoreComponent,
|
||||||
SearchCiphersPipe,
|
SearchCiphersPipe,
|
||||||
SettingsComponent,
|
SettingsComponent,
|
||||||
SsoComponent,
|
|
||||||
StopClickDirective,
|
StopClickDirective,
|
||||||
StopPropDirective,
|
StopPropDirective,
|
||||||
TabsComponent,
|
TabsComponent,
|
||||||
TwoFactorComponent,
|
|
||||||
TwoFactorOptionsComponent,
|
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
EnvironmentComponent,
|
EnvironmentComponent,
|
||||||
ModalComponent,
|
ModalComponent,
|
||||||
TwoFactorOptionsComponent,
|
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
|
|||||||
@@ -3,17 +3,17 @@ import {
|
|||||||
CanActivate,
|
CanActivate,
|
||||||
Router,
|
Router,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
|
import { ApiKeyService } from 'jslib/abstractions/apiKey.service';
|
||||||
|
|
||||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||||
import { UserService } from 'jslib/abstractions/user.service';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthGuardService implements CanActivate {
|
export class AuthGuardService implements CanActivate {
|
||||||
constructor(private userService: UserService, private router: Router,
|
constructor(private apiKeyService: ApiKeyService, private router: Router,
|
||||||
private messagingService: MessagingService) { }
|
private messagingService: MessagingService) { }
|
||||||
|
|
||||||
async canActivate() {
|
async canActivate() {
|
||||||
const isAuthed = await this.userService.isAuthenticated();
|
const isAuthed = await this.apiKeyService.isAuthenticated();
|
||||||
if (!isAuthed) {
|
if (!isAuthed) {
|
||||||
this.messagingService.send('logout');
|
this.messagingService.send('logout');
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ import {
|
|||||||
Router,
|
Router,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
|
|
||||||
import { UserService } from 'jslib/abstractions/user.service';
|
import { ApiKeyService } from 'jslib/abstractions/apiKey.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LaunchGuardService implements CanActivate {
|
export class LaunchGuardService implements CanActivate {
|
||||||
constructor(private userService: UserService, private router: Router) { }
|
constructor(private apiKeyService: ApiKeyService, private router: Router) { }
|
||||||
|
|
||||||
async canActivate() {
|
async canActivate() {
|
||||||
const isAuthed = await this.userService.isAuthenticated();
|
const isAuthed = await this.apiKeyService.isAuthenticated();
|
||||||
if (!isAuthed) {
|
if (!isAuthed) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
|
|||||||
import { ValidationService } from 'jslib/angular/services/validation.service';
|
import { ValidationService } from 'jslib/angular/services/validation.service';
|
||||||
|
|
||||||
import { ApiService } from 'jslib/services/api.service';
|
import { ApiService } from 'jslib/services/api.service';
|
||||||
|
import { ApiKeyService } from 'jslib/services/apiKey.service';
|
||||||
import { AppIdService } from 'jslib/services/appId.service';
|
import { AppIdService } from 'jslib/services/appId.service';
|
||||||
import { AuthService } from 'jslib/services/auth.service';
|
|
||||||
import { ConstantsService } from 'jslib/services/constants.service';
|
import { ConstantsService } from 'jslib/services/constants.service';
|
||||||
import { ContainerService } from 'jslib/services/container.service';
|
import { ContainerService } from 'jslib/services/container.service';
|
||||||
import { CryptoService } from 'jslib/services/crypto.service';
|
import { CryptoService } from 'jslib/services/crypto.service';
|
||||||
@@ -36,6 +36,7 @@ import { TokenService } from 'jslib/services/token.service';
|
|||||||
import { UserService } from 'jslib/services/user.service';
|
import { UserService } from 'jslib/services/user.service';
|
||||||
|
|
||||||
import { ApiService as ApiServiceAbstraction } from 'jslib/abstractions/api.service';
|
import { ApiService as ApiServiceAbstraction } from 'jslib/abstractions/api.service';
|
||||||
|
import { ApiKeyService as ApiKeyServiceAbstraction } from 'jslib/abstractions/apiKey.service';
|
||||||
import { AuthService as AuthServiceAbstraction } from 'jslib/abstractions/auth.service';
|
import { AuthService as AuthServiceAbstraction } from 'jslib/abstractions/auth.service';
|
||||||
import { CryptoService as CryptoServiceAbstraction } from 'jslib/abstractions/crypto.service';
|
import { CryptoService as CryptoServiceAbstraction } from 'jslib/abstractions/crypto.service';
|
||||||
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from 'jslib/abstractions/cryptoFunction.service';
|
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from 'jslib/abstractions/cryptoFunction.service';
|
||||||
@@ -53,6 +54,8 @@ import { StorageService as StorageServiceAbstraction } from 'jslib/abstractions/
|
|||||||
import { TokenService as TokenServiceAbstraction } from 'jslib/abstractions/token.service';
|
import { TokenService as TokenServiceAbstraction } from 'jslib/abstractions/token.service';
|
||||||
import { UserService as UserServiceAbstraction } from 'jslib/abstractions/user.service';
|
import { UserService as UserServiceAbstraction } from 'jslib/abstractions/user.service';
|
||||||
|
|
||||||
|
import { AuthService } from '../../services/auth.service';
|
||||||
|
|
||||||
const logService = new ElectronLogService();
|
const logService = new ElectronLogService();
|
||||||
const i18nService = new I18nService(window.navigator.language, './locales');
|
const i18nService = new I18nService(window.navigator.language, './locales');
|
||||||
const stateService = new StateService();
|
const stateService = new StateService();
|
||||||
@@ -70,9 +73,10 @@ const apiService = new ApiService(tokenService, platformUtilsService,
|
|||||||
async (expired: boolean) => messagingService.send('logout', { expired: expired }));
|
async (expired: boolean) => messagingService.send('logout', { expired: expired }));
|
||||||
const environmentService = new EnvironmentService(apiService, storageService, null);
|
const environmentService = new EnvironmentService(apiService, storageService, null);
|
||||||
const userService = new UserService(tokenService, storageService);
|
const userService = new UserService(tokenService, storageService);
|
||||||
|
const apiKeyService = new ApiKeyService(tokenService, storageService);
|
||||||
const containerService = new ContainerService(cryptoService);
|
const containerService = new ContainerService(cryptoService);
|
||||||
const authService = new AuthService(cryptoService, apiService, userService, tokenService, appIdService,
|
const authService = new AuthService(cryptoService, apiService, userService, tokenService, appIdService,
|
||||||
i18nService, platformUtilsService, messagingService, null, logService, false);
|
i18nService, platformUtilsService, messagingService, null, logService, apiKeyService, false);
|
||||||
const configurationService = new ConfigurationService(storageService, secureStorageService);
|
const configurationService = new ConfigurationService(storageService, secureStorageService);
|
||||||
const syncService = new SyncService(configurationService, logService, cryptoFunctionService, apiService,
|
const syncService = new SyncService(configurationService, logService, cryptoFunctionService, apiService,
|
||||||
messagingService, i18nService);
|
messagingService, i18nService);
|
||||||
@@ -130,6 +134,7 @@ export function initFactory(): Function {
|
|||||||
{ provide: PlatformUtilsServiceAbstraction, useValue: platformUtilsService },
|
{ provide: PlatformUtilsServiceAbstraction, useValue: platformUtilsService },
|
||||||
{ provide: ApiServiceAbstraction, useValue: apiService },
|
{ provide: ApiServiceAbstraction, useValue: apiService },
|
||||||
{ provide: UserServiceAbstraction, useValue: userService },
|
{ provide: UserServiceAbstraction, useValue: userService },
|
||||||
|
{ provide: ApiKeyServiceAbstraction, useValue: apiKeyService },
|
||||||
{ provide: MessagingServiceAbstraction, useValue: messagingService },
|
{ provide: MessagingServiceAbstraction, useValue: messagingService },
|
||||||
{ provide: BroadcasterService, useValue: broadcasterService },
|
{ provide: BroadcasterService, useValue: broadcasterService },
|
||||||
{ provide: StorageServiceAbstraction, useValue: storageService },
|
{ provide: StorageServiceAbstraction, useValue: storageService },
|
||||||
|
|||||||
@@ -213,17 +213,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card mb-3">
|
|
||||||
<h3 class="card-header">{{'account' | i18n}}</h3>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="organizationId">{{'organization' | i18n}}</label>
|
|
||||||
<select class="form-control" id="organizationId" name="OrganizationId" [(ngModel)]="organizationId">
|
|
||||||
<option *ngFor="let o of organizationOptions" [ngValue]="o.value">{{o.name}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
|||||||
@@ -37,9 +37,7 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
|||||||
okta = new OktaConfiguration();
|
okta = new OktaConfiguration();
|
||||||
oneLogin = new OneLoginConfiguration();
|
oneLogin = new OneLoginConfiguration();
|
||||||
sync = new SyncConfiguration();
|
sync = new SyncConfiguration();
|
||||||
organizationId: string;
|
|
||||||
directoryOptions: any[];
|
directoryOptions: any[];
|
||||||
organizationOptions: any[];
|
|
||||||
|
|
||||||
constructor(private i18nService: I18nService, private configurationService: ConfigurationService,
|
constructor(private i18nService: I18nService, private configurationService: ConfigurationService,
|
||||||
private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone,
|
private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone,
|
||||||
@@ -55,15 +53,6 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.organizationOptions = [{ name: this.i18nService.t('select'), value: null }];
|
|
||||||
const orgs = await this.stateService.get<ProfileOrganizationResponse[]>('profileOrganizations');
|
|
||||||
if (orgs != null) {
|
|
||||||
for (const org of orgs) {
|
|
||||||
this.organizationOptions.push({ name: org.name, value: org.id });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.organizationId = await this.configurationService.getOrganizationId();
|
|
||||||
this.directory = await this.configurationService.getDirectoryType();
|
this.directory = await this.configurationService.getDirectoryType();
|
||||||
this.ldap = (await this.configurationService.getDirectory<LdapConfiguration>(DirectoryType.Ldap)) ||
|
this.ldap = (await this.configurationService.getDirectory<LdapConfiguration>(DirectoryType.Ldap)) ||
|
||||||
this.ldap;
|
this.ldap;
|
||||||
@@ -87,7 +76,6 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
|||||||
if (this.ldap != null && this.ldap.ad) {
|
if (this.ldap != null && this.ldap.ad) {
|
||||||
this.ldap.pagedSearch = true;
|
this.ldap.pagedSearch = true;
|
||||||
}
|
}
|
||||||
await this.configurationService.saveOrganizationId(this.organizationId);
|
|
||||||
await this.configurationService.saveDirectoryType(this.directory);
|
await this.configurationService.saveDirectoryType(this.directory);
|
||||||
await this.configurationService.saveDirectory(DirectoryType.Ldap, this.ldap);
|
await this.configurationService.saveDirectory(DirectoryType.Ldap, this.ldap);
|
||||||
await this.configurationService.saveDirectory(DirectoryType.GSuite, this.gsuite);
|
await this.configurationService.saveDirectory(DirectoryType.GSuite, this.gsuite);
|
||||||
|
|||||||
13
src/bwdc.ts
13
src/bwdc.ts
@@ -3,7 +3,7 @@ import * as path from 'path';
|
|||||||
|
|
||||||
import { LogLevelType } from 'jslib/enums/logLevelType';
|
import { LogLevelType } from 'jslib/enums/logLevelType';
|
||||||
|
|
||||||
import { AuthService } from 'jslib/services/auth.service';
|
import { AuthService } from './services/auth.service';
|
||||||
|
|
||||||
import { ConfigurationService } from './services/configuration.service';
|
import { ConfigurationService } from './services/configuration.service';
|
||||||
import { I18nService } from './services/i18n.service';
|
import { I18nService } from './services/i18n.service';
|
||||||
@@ -14,6 +14,7 @@ import { SyncService } from './services/sync.service';
|
|||||||
import { CliPlatformUtilsService } from 'jslib/cli/services/cliPlatformUtils.service';
|
import { CliPlatformUtilsService } from 'jslib/cli/services/cliPlatformUtils.service';
|
||||||
import { ConsoleLogService } from 'jslib/cli/services/consoleLog.service';
|
import { ConsoleLogService } from 'jslib/cli/services/consoleLog.service';
|
||||||
|
|
||||||
|
import { ApiKeyService } from 'jslib/services/apiKey.service';
|
||||||
import { AppIdService } from 'jslib/services/appId.service';
|
import { AppIdService } from 'jslib/services/appId.service';
|
||||||
import { ConstantsService } from 'jslib/services/constants.service';
|
import { ConstantsService } from 'jslib/services/constants.service';
|
||||||
import { ContainerService } from 'jslib/services/container.service';
|
import { ContainerService } from 'jslib/services/container.service';
|
||||||
@@ -47,6 +48,7 @@ export class Main {
|
|||||||
appIdService: AppIdService;
|
appIdService: AppIdService;
|
||||||
apiService: NodeApiService;
|
apiService: NodeApiService;
|
||||||
environmentService: EnvironmentService;
|
environmentService: EnvironmentService;
|
||||||
|
apiKeyService: ApiKeyService;
|
||||||
userService: UserService;
|
userService: UserService;
|
||||||
containerService: ContainerService;
|
containerService: ContainerService;
|
||||||
cryptoFunctionService: NodeCryptoFunctionService;
|
cryptoFunctionService: NodeCryptoFunctionService;
|
||||||
@@ -91,11 +93,12 @@ export class Main {
|
|||||||
this.apiService = new NodeApiService(this.tokenService, this.platformUtilsService,
|
this.apiService = new NodeApiService(this.tokenService, this.platformUtilsService,
|
||||||
async (expired: boolean) => await this.logout());
|
async (expired: boolean) => await this.logout());
|
||||||
this.environmentService = new EnvironmentService(this.apiService, this.storageService, null);
|
this.environmentService = new EnvironmentService(this.apiService, this.storageService, null);
|
||||||
|
this.apiKeyService = new ApiKeyService(this.tokenService, this.storageService);
|
||||||
this.userService = new UserService(this.tokenService, this.storageService);
|
this.userService = new UserService(this.tokenService, this.storageService);
|
||||||
this.containerService = new ContainerService(this.cryptoService);
|
this.containerService = new ContainerService(this.cryptoService);
|
||||||
this.authService = new AuthService(this.cryptoService, this.apiService, this.userService, this.tokenService,
|
this.authService = new AuthService(this.cryptoService, this.apiService, this.userService, this.tokenService,
|
||||||
this.appIdService, this.i18nService, this.platformUtilsService, this.messagingService, null,
|
this.appIdService, this.i18nService, this.platformUtilsService, this.messagingService, null,
|
||||||
this.logService, false);
|
this.logService, this.apiKeyService, false);
|
||||||
this.configurationService = new ConfigurationService(this.storageService, this.secureStorageService,
|
this.configurationService = new ConfigurationService(this.storageService, this.secureStorageService,
|
||||||
process.env.BITWARDENCLI_CONNECTOR_PLAINTEXT_SECRETS !== 'true');
|
process.env.BITWARDENCLI_CONNECTOR_PLAINTEXT_SECRETS !== 'true');
|
||||||
this.syncService = new SyncService(this.configurationService, this.logService, this.cryptoFunctionService,
|
this.syncService = new SyncService(this.configurationService, this.logService, this.cryptoFunctionService,
|
||||||
@@ -110,10 +113,8 @@ export class Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async logout() {
|
async logout() {
|
||||||
await Promise.all([
|
await this.tokenService.clearToken();
|
||||||
this.tokenService.clearToken(),
|
await this.apiKeyService.clear();
|
||||||
this.userService.clear(),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async init() {
|
private async init() {
|
||||||
|
|||||||
@@ -20,12 +20,30 @@
|
|||||||
"emailRequired": {
|
"emailRequired": {
|
||||||
"message": "Email address is required."
|
"message": "Email address is required."
|
||||||
},
|
},
|
||||||
|
"clientIdRequired": {
|
||||||
|
"message": "Client Id is required."
|
||||||
|
},
|
||||||
|
"invalidClientId": {
|
||||||
|
"message": "Invalid Client Id provided."
|
||||||
|
},
|
||||||
|
"clientSecretRequired": {
|
||||||
|
"message": "Client Secret is required."
|
||||||
|
},
|
||||||
|
"orgApiKeyRequired": {
|
||||||
|
"message": "Api Key must belong to an Organization"
|
||||||
|
},
|
||||||
|
"failedToSaveCredentials": {
|
||||||
|
"message": "Failed to save credentials"
|
||||||
|
},
|
||||||
"invalidEmail": {
|
"invalidEmail": {
|
||||||
"message": "Invalid email address."
|
"message": "Invalid email address."
|
||||||
},
|
},
|
||||||
"masterPassRequired": {
|
"masterPassRequired": {
|
||||||
"message": "Master password is required."
|
"message": "Master password is required."
|
||||||
},
|
},
|
||||||
|
"missingRequiredInput": {
|
||||||
|
"message": "Missing required input."
|
||||||
|
},
|
||||||
"unexpectedError": {
|
"unexpectedError": {
|
||||||
"message": "An unexpected error has occurred."
|
"message": "An unexpected error has occurred."
|
||||||
},
|
},
|
||||||
@@ -575,7 +593,7 @@
|
|||||||
"message": "Welcome to the Bitwarden Directory Connector"
|
"message": "Welcome to the Bitwarden Directory Connector"
|
||||||
},
|
},
|
||||||
"logInDesc": {
|
"logInDesc": {
|
||||||
"message": "Log in as an organization admin user below."
|
"message": "Log in with an organization API key below."
|
||||||
},
|
},
|
||||||
"dirConfigIncomplete": {
|
"dirConfigIncomplete": {
|
||||||
"message": "Directory configuration incomplete."
|
"message": "Directory configuration incomplete."
|
||||||
|
|||||||
@@ -16,9 +16,12 @@ import { UpdateCommand } from 'jslib/cli/commands/update.command';
|
|||||||
|
|
||||||
import { BaseProgram } from 'jslib/cli/baseProgram';
|
import { BaseProgram } from 'jslib/cli/baseProgram';
|
||||||
|
|
||||||
|
import { ApiKeyService } from 'jslib/abstractions/apiKey.service';
|
||||||
import { Response } from 'jslib/cli/models/response';
|
import { Response } from 'jslib/cli/models/response';
|
||||||
import { StringResponse } from 'jslib/cli/models/response/stringResponse';
|
import { StringResponse } from 'jslib/cli/models/response/stringResponse';
|
||||||
|
|
||||||
|
import { Utils } from 'jslib/misc/utils';
|
||||||
|
|
||||||
const writeLn = (s: string, finalLine: boolean = false, error: boolean = false) => {
|
const writeLn = (s: string, finalLine: boolean = false, error: boolean = false) => {
|
||||||
const stream = error ? process.stderr : process.stdout;
|
const stream = error ? process.stderr : process.stdout;
|
||||||
if (finalLine && process.platform === 'win32') {
|
if (finalLine && process.platform === 'win32') {
|
||||||
@@ -29,8 +32,11 @@ const writeLn = (s: string, finalLine: boolean = false, error: boolean = false)
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class Program extends BaseProgram {
|
export class Program extends BaseProgram {
|
||||||
|
private apiKeyService: ApiKeyService;
|
||||||
|
|
||||||
constructor(private main: Main) {
|
constructor(private main: Main) {
|
||||||
super(main.userService, writeLn);
|
super(main.userService, writeLn);
|
||||||
|
this.apiKeyService = main.apiKeyService;
|
||||||
}
|
}
|
||||||
|
|
||||||
async run() {
|
async run() {
|
||||||
@@ -86,34 +92,26 @@ export class Program extends BaseProgram {
|
|||||||
});
|
});
|
||||||
|
|
||||||
program
|
program
|
||||||
.command('login [email] [password]')
|
.command('login [clientId] [clientSecret]')
|
||||||
.description('Log into a user account.')
|
.description('Log into an organization account.', {
|
||||||
.option('--method <method>', 'Two-step login method.')
|
clientId: 'Client_id part of your organization\'s API key',
|
||||||
.option('--code <code>', 'Two-step login code.')
|
clientSecret: 'Client_secret part of your organization\'s API key',
|
||||||
.option('--sso', 'Log in with Single-Sign On.')
|
|
||||||
.option('--passwordenv <variable-name>', 'Read password from the named environment variable.')
|
|
||||||
.option('--passwordfile <filename>', 'Read password from first line of the named file.')
|
|
||||||
.on('--help', () => {
|
|
||||||
writeLn('\n Notes:');
|
|
||||||
writeLn('');
|
|
||||||
writeLn(' See docs for valid `method` enum values.');
|
|
||||||
writeLn('');
|
|
||||||
writeLn(' Examples:');
|
|
||||||
writeLn('');
|
|
||||||
writeLn(' bwdc login');
|
|
||||||
writeLn(' bwdc login john@example.com myPassword321');
|
|
||||||
writeLn(' bwdc login john@example.com myPassword321 --method 1 --code 249213');
|
|
||||||
writeLn(' bwdc login john@example.com --passwordfile passwd.txt --method 1 --code 249213');
|
|
||||||
writeLn(' bwdc login john@example.com --passwordenv MY_PASSWD --method 1 --code 249213');
|
|
||||||
writeLn(' bwdc login --sso');
|
|
||||||
writeLn('', true);
|
|
||||||
})
|
})
|
||||||
.action(async (email: string, password: string, options: program.OptionValues) => {
|
.action(async (clientId: string, clientSecret: string, options: program.OptionValues) => {
|
||||||
await this.exitIfAuthed();
|
await this.exitIfAuthed();
|
||||||
const command = new LoginCommand(this.main.authService, this.main.apiService, this.main.i18nService,
|
const command = new LoginCommand(this.main.authService, this.main.apiService, this.main.i18nService,
|
||||||
this.main.environmentService, this.main.passwordGenerationService, this.main.cryptoFunctionService,
|
this.main.environmentService, this.main.passwordGenerationService, this.main.cryptoFunctionService,
|
||||||
this.main.platformUtilsService, 'connector');
|
this.main.platformUtilsService, 'connector');
|
||||||
const response = await command.run(email, password, options);
|
|
||||||
|
if (!Utils.isNullOrWhitespace(clientId)) {
|
||||||
|
process.env.BW_CLIENTID = clientId;
|
||||||
|
}
|
||||||
|
if (!Utils.isNullOrWhitespace(clientSecret)) {
|
||||||
|
process.env.BW_CLIENTSECRET = clientSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
options = Object.assign(options ?? {}, { apikey: true }); // force apikey use
|
||||||
|
const response = await command.run(null, null, options);
|
||||||
this.processResponse(response);
|
this.processResponse(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -284,4 +282,20 @@ export class Program extends BaseProgram {
|
|||||||
program.outputHelp();
|
program.outputHelp();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async exitIfAuthed() {
|
||||||
|
const authed = await this.apiKeyService.isAuthenticated();
|
||||||
|
if (authed) {
|
||||||
|
const type = await this.apiKeyService.getEntityType();
|
||||||
|
const id = await this.apiKeyService.getEntityId();
|
||||||
|
this.processResponse(Response.error('You are already logged in as ' + type + '.' + id + '.'), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async exitIfNotAuthed() {
|
||||||
|
const authed = await this.apiKeyService.isAuthenticated();
|
||||||
|
if (!authed) {
|
||||||
|
this.processResponse(Response.error('You are not logged in.'), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
56
src/services/auth.service.ts
Normal file
56
src/services/auth.service.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { ApiService } from 'jslib/abstractions/api.service';
|
||||||
|
import { ApiKeyService } from 'jslib/abstractions/apiKey.service';
|
||||||
|
import { AppIdService } from 'jslib/abstractions/appId.service';
|
||||||
|
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||||
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
|
import { LogService } from 'jslib/abstractions/log.service';
|
||||||
|
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||||
|
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||||
|
import { TokenService } from 'jslib/abstractions/token.service';
|
||||||
|
import { UserService } from 'jslib/abstractions/user.service';
|
||||||
|
import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service';
|
||||||
|
|
||||||
|
import { AuthService as AuthServiceBase } from 'jslib/services/auth.service';
|
||||||
|
|
||||||
|
import { AuthResult } from 'jslib/models/domain';
|
||||||
|
import { DeviceRequest } from 'jslib/models/request/deviceRequest';
|
||||||
|
import { TokenRequest } from 'jslib/models/request/tokenRequest';
|
||||||
|
import { IdentityTokenResponse } from 'jslib/models/response/identityTokenResponse';
|
||||||
|
|
||||||
|
export class AuthService extends AuthServiceBase {
|
||||||
|
|
||||||
|
constructor(cryptoService: CryptoService, apiService: ApiService, userService: UserService,
|
||||||
|
tokenService: TokenService, appIdService: AppIdService, i18nService: I18nService,
|
||||||
|
platformUtilsService: PlatformUtilsService, messagingService: MessagingService,
|
||||||
|
vaultTimeoutService: VaultTimeoutService, logService: LogService, private apiKeyService: ApiKeyService,
|
||||||
|
setCryptoKeys = true) {
|
||||||
|
super(cryptoService, apiService, userService, tokenService, appIdService, i18nService, platformUtilsService,
|
||||||
|
messagingService, vaultTimeoutService, logService, setCryptoKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
async logInApiKey(clientId: string, clientSecret: string): Promise<AuthResult> {
|
||||||
|
this.selectedTwoFactorProviderType = null;
|
||||||
|
if (clientId.startsWith('organization')) {
|
||||||
|
return await this.organizationLogInHelper(clientId, clientSecret);
|
||||||
|
}
|
||||||
|
return await super.logInApiKey(clientId, clientSecret);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async organizationLogInHelper(clientId: string, clientSecret: string) {
|
||||||
|
const appId = await this.appIdService.getAppId();
|
||||||
|
const deviceRequest = new DeviceRequest(appId, this.platformUtilsService);
|
||||||
|
const request = new TokenRequest(null, null, [clientId, clientSecret], null,
|
||||||
|
null, false, deviceRequest);
|
||||||
|
|
||||||
|
const response = await this.apiService.postIdentityToken(request);
|
||||||
|
const result = new AuthResult();
|
||||||
|
result.twoFactor = !(response as any).accessToken;
|
||||||
|
|
||||||
|
const tokenResponse = response as IdentityTokenResponse;
|
||||||
|
result.resetMasterPassword = tokenResponse.resetMasterPassword;
|
||||||
|
await this.tokenService.setToken(tokenResponse.accessToken);
|
||||||
|
await this.apiKeyService.setInformation(clientId);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,9 +4,7 @@ import { GroupEntry } from '../models/groupEntry';
|
|||||||
import { SyncConfiguration } from '../models/syncConfiguration';
|
import { SyncConfiguration } from '../models/syncConfiguration';
|
||||||
import { UserEntry } from '../models/userEntry';
|
import { UserEntry } from '../models/userEntry';
|
||||||
|
|
||||||
import { ImportDirectoryRequest } from 'jslib/models/request/importDirectoryRequest';
|
import { OrganizationImportRequest } from 'jslib/models/request/organizationImportRequest';
|
||||||
import { ImportDirectoryRequestGroup } from 'jslib/models/request/importDirectoryRequestGroup';
|
|
||||||
import { ImportDirectoryRequestUser } from 'jslib/models/request/importDirectoryRequestUser';
|
|
||||||
|
|
||||||
import { ApiService } from 'jslib/abstractions/api.service';
|
import { ApiService } from 'jslib/abstractions/api.service';
|
||||||
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
|
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
|
||||||
@@ -58,7 +56,7 @@ export class SyncService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (test || (!syncConfig.overwriteExisting &&
|
if (test || (!syncConfig.overwriteExisting &&
|
||||||
(groups == null || groups.length === 0) && (users == null || users.length === 0))) {
|
(groups == null || groups.length === 0) && (users == null || users.length === 0))) {
|
||||||
if (!test) {
|
if (!test) {
|
||||||
await this.saveSyncTimes(syncConfig, now);
|
await this.saveSyncTimes(syncConfig, now);
|
||||||
}
|
}
|
||||||
@@ -89,7 +87,7 @@ export class SyncService {
|
|||||||
const lastHash = await this.configurationService.getLastSyncHash();
|
const lastHash = await this.configurationService.getLastSyncHash();
|
||||||
|
|
||||||
if (lastHash == null || (hash !== lastHash && hashLegacy !== lastHash)) {
|
if (lastHash == null || (hash !== lastHash && hashLegacy !== lastHash)) {
|
||||||
await this.apiService.postImportDirectory(orgId, req);
|
await this.apiService.postPublicImportDirectory(req);
|
||||||
await this.configurationService.saveLastSyncHash(hash);
|
await this.configurationService.saveLastSyncHash(hash);
|
||||||
} else {
|
} else {
|
||||||
groups = null;
|
groups = null;
|
||||||
@@ -145,36 +143,26 @@ export class SyncService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildRequest(groups: GroupEntry[], users: UserEntry[], removeDisabled: boolean,
|
private buildRequest(groups: GroupEntry[], users: UserEntry[], removeDisabled: boolean, overwriteExisting: boolean,
|
||||||
overwriteExisting: boolean, largeImport: boolean): ImportDirectoryRequest {
|
largeImport: boolean = false) {
|
||||||
const model = new ImportDirectoryRequest();
|
return new OrganizationImportRequest({
|
||||||
model.overwriteExisting = overwriteExisting;
|
groups: (groups ?? []).map(g => {
|
||||||
model.largeImport = largeImport;
|
return {
|
||||||
|
name: g.name,
|
||||||
if (groups != null) {
|
externalId: g.externalId,
|
||||||
for (const g of groups) {
|
memberExternalIds: Array.from(g.userMemberExternalIds),
|
||||||
const ig = new ImportDirectoryRequestGroup();
|
};
|
||||||
ig.name = g.name;
|
}),
|
||||||
ig.externalId = g.externalId;
|
users: (users ?? []).map(u => {
|
||||||
ig.users = Array.from(g.userMemberExternalIds);
|
return {
|
||||||
model.groups.push(ig);
|
email: u.email,
|
||||||
}
|
externalId: u.externalId,
|
||||||
}
|
deleted: u.deleted || (removeDisabled && u.disabled),
|
||||||
|
};
|
||||||
if (users != null) {
|
}),
|
||||||
for (const u of users) {
|
overwriteExisting: overwriteExisting,
|
||||||
const iu = new ImportDirectoryRequestUser();
|
largeImport: largeImport,
|
||||||
iu.email = u.email;
|
});
|
||||||
if (iu.email != null) {
|
|
||||||
iu.email = iu.email.trim().toLowerCase();
|
|
||||||
}
|
|
||||||
iu.externalId = u.externalId;
|
|
||||||
iu.deleted = u.deleted || (removeDisabled && u.disabled);
|
|
||||||
model.users.push(iu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return model;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async saveSyncTimes(syncConfig: SyncConfiguration, time: Date) {
|
private async saveSyncTimes(syncConfig: SyncConfiguration, time: Date) {
|
||||||
|
|||||||
Reference in New Issue
Block a user