Implement User-based API Keys (#688)
* refactored api key modal for multiple key types * Added support for viewing and rotating user API keys * Fixed the API key component references in app.module * Implemented User ApiKey viewing/rotating * Changed ApiKey grant_type display to client_credentials * Hopefully put jslib back * Added new localization strings for user API keys * Toggled button text based on if viewing or rotating an api key * updated jslib * Reverted jslib * Trying to fix jslib * Reverted jslib from commit hash * Reupdated jslib
This commit is contained in:
parent
37cf46d581
commit
759dc647e5
2
jslib
2
jslib
|
@ -1 +1 @@
|
||||||
Subproject commit 9aa3cbf73d9df9a2641654270911359593bcb5c5
|
Subproject commit 79b856cb6e73f126a263a0e4a61d0161828a40dd
|
|
@ -60,13 +60,11 @@ import { UserGroupsComponent as OrgUserGroupsComponent } from './organizations/m
|
||||||
|
|
||||||
import { AccountComponent as OrgAccountComponent } from './organizations/settings/account.component';
|
import { AccountComponent as OrgAccountComponent } from './organizations/settings/account.component';
|
||||||
import { AdjustSeatsComponent } from './organizations/settings/adjust-seats.component';
|
import { AdjustSeatsComponent } from './organizations/settings/adjust-seats.component';
|
||||||
import { ApiKeyComponent as OrgApiKeyComponent } from './organizations/settings/api-key.component';
|
|
||||||
import { ChangePlanComponent } from './organizations/settings/change-plan.component';
|
import { ChangePlanComponent } from './organizations/settings/change-plan.component';
|
||||||
import { DeleteOrganizationComponent } from './organizations/settings/delete-organization.component';
|
import { DeleteOrganizationComponent } from './organizations/settings/delete-organization.component';
|
||||||
import { DownloadLicenseComponent } from './organizations/settings/download-license.component';
|
import { DownloadLicenseComponent } from './organizations/settings/download-license.component';
|
||||||
import { OrganizationBillingComponent } from './organizations/settings/organization-billing.component';
|
import { OrganizationBillingComponent } from './organizations/settings/organization-billing.component';
|
||||||
import { OrganizationSubscriptionComponent } from './organizations/settings/organization-subscription.component';
|
import { OrganizationSubscriptionComponent } from './organizations/settings/organization-subscription.component';
|
||||||
import { RotateApiKeyComponent as OrgRotateApiKeyComponent } from './organizations/settings/rotate-api-key.component';
|
|
||||||
import { SettingsComponent as OrgSettingComponent } from './organizations/settings/settings.component';
|
import { SettingsComponent as OrgSettingComponent } from './organizations/settings/settings.component';
|
||||||
import {
|
import {
|
||||||
TwoFactorSetupComponent as OrgTwoFactorSetupComponent,
|
TwoFactorSetupComponent as OrgTwoFactorSetupComponent,
|
||||||
|
@ -106,6 +104,7 @@ import { AccountComponent } from './settings/account.component';
|
||||||
import { AddCreditComponent } from './settings/add-credit.component';
|
import { AddCreditComponent } from './settings/add-credit.component';
|
||||||
import { AdjustPaymentComponent } from './settings/adjust-payment.component';
|
import { AdjustPaymentComponent } from './settings/adjust-payment.component';
|
||||||
import { AdjustStorageComponent } from './settings/adjust-storage.component';
|
import { AdjustStorageComponent } from './settings/adjust-storage.component';
|
||||||
|
import { ApiKeyComponent } from './settings/api-key.component';
|
||||||
import { ChangeEmailComponent } from './settings/change-email.component';
|
import { ChangeEmailComponent } from './settings/change-email.component';
|
||||||
import { ChangeKdfComponent } from './settings/change-kdf.component';
|
import { ChangeKdfComponent } from './settings/change-kdf.component';
|
||||||
import { ChangePasswordComponent } from './settings/change-password.component';
|
import { ChangePasswordComponent } from './settings/change-password.component';
|
||||||
|
@ -269,6 +268,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||||
AdjustSeatsComponent,
|
AdjustSeatsComponent,
|
||||||
AdjustStorageComponent,
|
AdjustStorageComponent,
|
||||||
ApiActionDirective,
|
ApiActionDirective,
|
||||||
|
ApiKeyComponent,
|
||||||
AppComponent,
|
AppComponent,
|
||||||
AttachmentsComponent,
|
AttachmentsComponent,
|
||||||
AutofocusDirective,
|
AutofocusDirective,
|
||||||
|
@ -316,7 +316,6 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||||
OptionsComponent,
|
OptionsComponent,
|
||||||
OrgAccountComponent,
|
OrgAccountComponent,
|
||||||
OrgAddEditComponent,
|
OrgAddEditComponent,
|
||||||
OrgApiKeyComponent,
|
|
||||||
OrganizationBillingComponent,
|
OrganizationBillingComponent,
|
||||||
OrganizationPlansComponent,
|
OrganizationPlansComponent,
|
||||||
OrganizationSubscriptionComponent,
|
OrganizationSubscriptionComponent,
|
||||||
|
@ -340,7 +339,6 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||||
OrgPolicyEditComponent,
|
OrgPolicyEditComponent,
|
||||||
OrgPoliciesComponent,
|
OrgPoliciesComponent,
|
||||||
OrgReusedPasswordsReportComponent,
|
OrgReusedPasswordsReportComponent,
|
||||||
OrgRotateApiKeyComponent,
|
|
||||||
OrgSettingComponent,
|
OrgSettingComponent,
|
||||||
OrgToolsComponent,
|
OrgToolsComponent,
|
||||||
OrgTwoFactorSetupComponent,
|
OrgTwoFactorSetupComponent,
|
||||||
|
@ -400,6 +398,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
AddEditComponent,
|
AddEditComponent,
|
||||||
|
ApiKeyComponent,
|
||||||
AttachmentsComponent,
|
AttachmentsComponent,
|
||||||
BulkActionsComponent,
|
BulkActionsComponent,
|
||||||
BulkDeleteComponent,
|
BulkDeleteComponent,
|
||||||
|
@ -413,7 +412,6 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||||
FolderAddEditComponent,
|
FolderAddEditComponent,
|
||||||
ModalComponent,
|
ModalComponent,
|
||||||
OrgAddEditComponent,
|
OrgAddEditComponent,
|
||||||
OrgApiKeyComponent,
|
|
||||||
OrgAttachmentsComponent,
|
OrgAttachmentsComponent,
|
||||||
OrgCollectionAddEditComponent,
|
OrgCollectionAddEditComponent,
|
||||||
OrgCollectionsComponent,
|
OrgCollectionsComponent,
|
||||||
|
@ -421,7 +419,6 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||||
OrgEntityUsersComponent,
|
OrgEntityUsersComponent,
|
||||||
OrgGroupAddEditComponent,
|
OrgGroupAddEditComponent,
|
||||||
OrgPolicyEditComponent,
|
OrgPolicyEditComponent,
|
||||||
OrgRotateApiKeyComponent,
|
|
||||||
OrgUserAddEditComponent,
|
OrgUserAddEditComponent,
|
||||||
OrgUserConfirmComponent,
|
OrgUserConfirmComponent,
|
||||||
OrgUserGroupsComponent,
|
OrgUserGroupsComponent,
|
||||||
|
|
|
@ -18,11 +18,10 @@ import { OrganizationUpdateRequest } from 'jslib/models/request/organizationUpda
|
||||||
import { OrganizationResponse } from 'jslib/models/response/organizationResponse';
|
import { OrganizationResponse } from 'jslib/models/response/organizationResponse';
|
||||||
|
|
||||||
import { ModalComponent } from '../../modal.component';
|
import { ModalComponent } from '../../modal.component';
|
||||||
|
import { ApiKeyComponent } from '../../settings/api-key.component';
|
||||||
import { PurgeVaultComponent } from '../../settings/purge-vault.component';
|
import { PurgeVaultComponent } from '../../settings/purge-vault.component';
|
||||||
import { TaxInfoComponent } from '../../settings/tax-info.component';
|
import { TaxInfoComponent } from '../../settings/tax-info.component';
|
||||||
import { ApiKeyComponent } from './api-key.component';
|
|
||||||
import { DeleteOrganizationComponent } from './delete-organization.component';
|
import { DeleteOrganizationComponent } from './delete-organization.component';
|
||||||
import { RotateApiKeyComponent } from './rotate-api-key.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-org-account',
|
selector: 'app-org-account',
|
||||||
|
@ -125,7 +124,14 @@ export class AccountComponent {
|
||||||
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
||||||
this.modal = this.apiKeyModalRef.createComponent(factory).instance;
|
this.modal = this.apiKeyModalRef.createComponent(factory).instance;
|
||||||
const childComponent = this.modal.show<ApiKeyComponent>(ApiKeyComponent, this.apiKeyModalRef);
|
const childComponent = this.modal.show<ApiKeyComponent>(ApiKeyComponent, this.apiKeyModalRef);
|
||||||
childComponent.organizationId = this.organizationId;
|
childComponent.keyType = 'organization';
|
||||||
|
childComponent.entityId = this.organizationId;
|
||||||
|
childComponent.postKey = this.apiService.postOrganizationApiKey.bind(this.apiService);
|
||||||
|
childComponent.scope = 'api.organization';
|
||||||
|
childComponent.grantType = 'client_credentials';
|
||||||
|
childComponent.apiKeyTitle = 'apiKey';
|
||||||
|
childComponent.apiKeyWarning = 'apiKeyWarning';
|
||||||
|
childComponent.apiKeyDescription = 'apiKeyDesc';
|
||||||
|
|
||||||
this.modal.onClosed.subscribe(async () => {
|
this.modal.onClosed.subscribe(async () => {
|
||||||
this.modal = null;
|
this.modal = null;
|
||||||
|
@ -139,8 +145,16 @@ export class AccountComponent {
|
||||||
|
|
||||||
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
||||||
this.modal = this.rotateApiKeyModalRef.createComponent(factory).instance;
|
this.modal = this.rotateApiKeyModalRef.createComponent(factory).instance;
|
||||||
const childComponent = this.modal.show<RotateApiKeyComponent>(RotateApiKeyComponent, this.rotateApiKeyModalRef);
|
const childComponent = this.modal.show<ApiKeyComponent>(ApiKeyComponent, this.rotateApiKeyModalRef);
|
||||||
childComponent.organizationId = this.organizationId;
|
childComponent.keyType = 'organization';
|
||||||
|
childComponent.isRotation = true;
|
||||||
|
childComponent.entityId = this.organizationId;
|
||||||
|
childComponent.postKey = this.apiService.postOrganizationRotateApiKey.bind(this.apiService);
|
||||||
|
childComponent.scope = 'api.organization';
|
||||||
|
childComponent.grantType = 'client_credentials';
|
||||||
|
childComponent.apiKeyTitle = 'apiKey';
|
||||||
|
childComponent.apiKeyWarning = 'apiKeyWarning';
|
||||||
|
childComponent.apiKeyDescription = 'apiKeyRotateDesc';
|
||||||
|
|
||||||
this.modal.onClosed.subscribe(async () => {
|
this.modal.onClosed.subscribe(async () => {
|
||||||
this.modal = null;
|
this.modal = null;
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="rotateKeyTitle">
|
|
||||||
<div class="modal-dialog" role="document">
|
|
||||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
|
||||||
<div class="modal-header">
|
|
||||||
<h2 class="modal-title" id="rotateKeyTitle">{{'rotateApiKey' | i18n}}</h2>
|
|
||||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>{{'apiKeyRotateDesc' | i18n}}</p>
|
|
||||||
<ng-container *ngIf="!clientSecret">
|
|
||||||
<label for="masterPassword">{{'masterPass' | i18n}}</label>
|
|
||||||
<input id="masterPassword" type="password" name="MasterPasswordHash" class="form-control"
|
|
||||||
[(ngModel)]="masterPassword" required appAutofocus appInputVerbatim>
|
|
||||||
</ng-container>
|
|
||||||
<app-callout type="warning" *ngIf="clientSecret">{{'apiKeyWarning' | i18n}}</app-callout>
|
|
||||||
<app-callout type="info" title="{{'oauth2ClientCredentials' | i18n}}" icon="fa-key"
|
|
||||||
*ngIf="clientSecret">
|
|
||||||
<p class="mb-1">
|
|
||||||
<strong>client_id:</strong><br>
|
|
||||||
<code>{{clientId}}</code>
|
|
||||||
</p>
|
|
||||||
<p class="mb-1">
|
|
||||||
<strong>client_secret:</strong><br>
|
|
||||||
<code>{{clientSecret}}</code>
|
|
||||||
</p>
|
|
||||||
<p class="mb-1">
|
|
||||||
<strong>scope:</strong><br>
|
|
||||||
<code>{{scope}}</code>
|
|
||||||
</p>
|
|
||||||
<p class="mb-0">
|
|
||||||
<strong>grant_type:</strong><br>
|
|
||||||
<code>client_credentials</code>
|
|
||||||
</p>
|
|
||||||
</app-callout>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading"
|
|
||||||
*ngIf="!clientSecret">
|
|
||||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
|
||||||
<span>{{'rotateApiKey' | i18n}}</span>
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,50 +0,0 @@
|
||||||
import { Component } from '@angular/core';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
|
|
||||||
import { ToasterService } from 'angular2-toaster';
|
|
||||||
import { Angulartics2 } from 'angulartics2';
|
|
||||||
|
|
||||||
import { ApiService } from 'jslib/abstractions/api.service';
|
|
||||||
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
|
||||||
|
|
||||||
import { PasswordVerificationRequest } from 'jslib/models/request/passwordVerificationRequest';
|
|
||||||
|
|
||||||
import { ApiKeyResponse } from 'jslib/models/response/apiKeyResponse';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-rotate-api-key',
|
|
||||||
templateUrl: 'rotate-api-key.component.html',
|
|
||||||
})
|
|
||||||
export class RotateApiKeyComponent {
|
|
||||||
organizationId: string;
|
|
||||||
|
|
||||||
masterPassword: string;
|
|
||||||
formPromise: Promise<ApiKeyResponse>;
|
|
||||||
clientId: string;
|
|
||||||
clientSecret: string;
|
|
||||||
scope: string;
|
|
||||||
|
|
||||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
|
||||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
|
||||||
private cryptoService: CryptoService, private router: Router) { }
|
|
||||||
|
|
||||||
async submit() {
|
|
||||||
if (this.masterPassword == null || this.masterPassword === '') {
|
|
||||||
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
|
|
||||||
this.i18nService.t('masterPassRequired'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const request = new PasswordVerificationRequest();
|
|
||||||
request.masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, null);
|
|
||||||
try {
|
|
||||||
this.formPromise = this.apiService.postOrganizationRotateApiKey(this.organizationId, request);
|
|
||||||
const response = await this.formPromise;
|
|
||||||
this.clientSecret = response.apiKey;
|
|
||||||
this.clientId = 'organization.' + this.organizationId;
|
|
||||||
this.scope = 'api.organization';
|
|
||||||
this.analytics.eventTrack.next({ action: 'Rotated Organization API Key' });
|
|
||||||
} catch { }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,6 +14,14 @@
|
||||||
<h1>{{'encKeySettings' | i18n}}</h1>
|
<h1>{{'encKeySettings' | i18n}}</h1>
|
||||||
</div>
|
</div>
|
||||||
<app-change-kdf></app-change-kdf>
|
<app-change-kdf></app-change-kdf>
|
||||||
|
<div class="secondary-header border-0 mb-0">
|
||||||
|
<h1>{{'apiKey' | i18n}}</h1>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
{{'userApiKeyDesc' | i18n}}
|
||||||
|
</p>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" (click)="viewUserApiKey()">{{'viewApiKey' | i18n}}</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" (click)="rotateUserApiKey()">{{'rotateApiKey' | i18n}}</button>
|
||||||
<div class="secondary-header text-danger border-0 mb-0">
|
<div class="secondary-header text-danger border-0 mb-0">
|
||||||
<h1>{{'dangerZone' | i18n}}</h1>
|
<h1>{{'dangerZone' | i18n}}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
@ -30,3 +38,5 @@
|
||||||
<ng-template #deauthorizeSessionsTemplate></ng-template>
|
<ng-template #deauthorizeSessionsTemplate></ng-template>
|
||||||
<ng-template #purgeVaultTemplate></ng-template>
|
<ng-template #purgeVaultTemplate></ng-template>
|
||||||
<ng-template #deleteAccountTemplate></ng-template>
|
<ng-template #deleteAccountTemplate></ng-template>
|
||||||
|
<ng-template #viewUserApiKeyTemplate></ng-template>
|
||||||
|
<ng-template #rotateUserApiKeyTemplate></ng-template>
|
||||||
|
|
|
@ -6,10 +6,14 @@ import {
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
|
||||||
import { ModalComponent } from '../modal.component';
|
import { ModalComponent } from '../modal.component';
|
||||||
|
import { ApiKeyComponent } from './api-key.component';
|
||||||
import { DeauthorizeSessionsComponent } from './deauthorize-sessions.component';
|
import { DeauthorizeSessionsComponent } from './deauthorize-sessions.component';
|
||||||
import { DeleteAccountComponent } from './delete-account.component';
|
import { DeleteAccountComponent } from './delete-account.component';
|
||||||
import { PurgeVaultComponent } from './purge-vault.component';
|
import { PurgeVaultComponent } from './purge-vault.component';
|
||||||
|
|
||||||
|
import { ApiService } from 'jslib/abstractions/api.service';
|
||||||
|
import { UserService } from 'jslib/abstractions/user.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-account',
|
selector: 'app-account',
|
||||||
templateUrl: 'account.component.html',
|
templateUrl: 'account.component.html',
|
||||||
|
@ -18,10 +22,13 @@ export class AccountComponent {
|
||||||
@ViewChild('deauthorizeSessionsTemplate', { read: ViewContainerRef, static: true }) deauthModalRef: ViewContainerRef;
|
@ViewChild('deauthorizeSessionsTemplate', { read: ViewContainerRef, static: true }) deauthModalRef: ViewContainerRef;
|
||||||
@ViewChild('purgeVaultTemplate', { read: ViewContainerRef, static: true }) purgeModalRef: ViewContainerRef;
|
@ViewChild('purgeVaultTemplate', { read: ViewContainerRef, static: true }) purgeModalRef: ViewContainerRef;
|
||||||
@ViewChild('deleteAccountTemplate', { read: ViewContainerRef, static: true }) deleteModalRef: ViewContainerRef;
|
@ViewChild('deleteAccountTemplate', { read: ViewContainerRef, static: true }) deleteModalRef: ViewContainerRef;
|
||||||
|
@ViewChild('viewUserApiKeyTemplate', { read: ViewContainerRef, static: true }) viewUserApiKeyModalRef: ViewContainerRef;
|
||||||
|
@ViewChild('rotateUserApiKeyTemplate', { read: ViewContainerRef, static: true }) rotateUserApiKeyModalRef: ViewContainerRef;
|
||||||
|
|
||||||
private modal: ModalComponent = null;
|
private modal: ModalComponent = null;
|
||||||
|
|
||||||
constructor(private componentFactoryResolver: ComponentFactoryResolver) { }
|
constructor(private componentFactoryResolver: ComponentFactoryResolver, private apiService: ApiService,
|
||||||
|
private userService: UserService) { }
|
||||||
|
|
||||||
deauthorizeSessions() {
|
deauthorizeSessions() {
|
||||||
if (this.modal != null) {
|
if (this.modal != null) {
|
||||||
|
@ -64,4 +71,49 @@ export class AccountComponent {
|
||||||
this.modal = null;
|
this.modal = null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async viewUserApiKey() {
|
||||||
|
if (this.modal != null) {
|
||||||
|
this.modal.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
||||||
|
this.modal = this.viewUserApiKeyModalRef.createComponent(factory).instance;
|
||||||
|
const childComponent = this.modal.show<ApiKeyComponent>(ApiKeyComponent, this.viewUserApiKeyModalRef);
|
||||||
|
childComponent.keyType = 'user';
|
||||||
|
childComponent.entityId = await this.userService.getUserId();
|
||||||
|
childComponent.postKey = this.apiService.postUserApiKey.bind(this.apiService);
|
||||||
|
childComponent.scope = 'api';
|
||||||
|
childComponent.grantType = 'client_credentials';
|
||||||
|
childComponent.apiKeyTitle = 'apiKey';
|
||||||
|
childComponent.apiKeyWarning = 'userApiKeyWarning';
|
||||||
|
childComponent.apiKeyDescription = 'userApiKeyDesc';
|
||||||
|
|
||||||
|
this.modal.onClosed.subscribe(async () => {
|
||||||
|
this.modal = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async rotateUserApiKey() {
|
||||||
|
if (this.modal != null) {
|
||||||
|
this.modal.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
||||||
|
this.modal = this.rotateUserApiKeyModalRef.createComponent(factory).instance;
|
||||||
|
const childComponent = this.modal.show<ApiKeyComponent>(ApiKeyComponent, this.rotateUserApiKeyModalRef);
|
||||||
|
childComponent.keyType = 'user';
|
||||||
|
childComponent.isRotation = true;
|
||||||
|
childComponent.entityId = await this.userService.getUserId();
|
||||||
|
childComponent.postKey = this.apiService.postUserRotateApiKey.bind(this.apiService);
|
||||||
|
childComponent.scope = 'api';
|
||||||
|
childComponent.grantType = 'client_credentials';
|
||||||
|
childComponent.apiKeyTitle = 'apiKey';
|
||||||
|
childComponent.apiKeyWarning = 'userApiKeyWarning';
|
||||||
|
childComponent.apiKeyDescription = 'apiKeyRotateDesc';
|
||||||
|
|
||||||
|
this.modal.onClosed.subscribe(async () => {
|
||||||
|
this.modal = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,19 +2,19 @@
|
||||||
<div class="modal-dialog" role="document">
|
<div class="modal-dialog" role="document">
|
||||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h2 class="modal-title" id="apiKeyTitle">{{'apiKey' | i18n}}</h2>
|
<h2 class="modal-title" id="apiKeyTitle">{{apiKeyTitle | i18n}}</h2>
|
||||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||||
<span aria-hidden="true">×</span>
|
<span aria-hidden="true">×</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<p>{{'apiKeyDesc' | i18n}}</p>
|
<p>{{apiKeyDescription | i18n}}</p>
|
||||||
<ng-container *ngIf="!clientSecret">
|
<ng-container *ngIf="!clientSecret">
|
||||||
<label for="masterPassword">{{'masterPass' | i18n}}</label>
|
<label for="masterPassword">{{'masterPass' | i18n}}</label>
|
||||||
<input id="masterPassword" type="password" name="MasterPasswordHash" class="form-control"
|
<input id="masterPassword" type="password" name="MasterPasswordHash" class="form-control"
|
||||||
[(ngModel)]="masterPassword" required appAutofocus appInputVerbatim>
|
[(ngModel)]="masterPassword" required appAutofocus appInputVerbatim>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<app-callout type="warning" *ngIf="clientSecret">{{'apiKeyWarning' | i18n}}</app-callout>
|
<app-callout type="warning" *ngIf="clientSecret">{{apiKeyWarning | i18n}}</app-callout>
|
||||||
<app-callout type="info" title="{{'oauth2ClientCredentials' | i18n}}" icon="fa-key"
|
<app-callout type="info" title="{{'oauth2ClientCredentials' | i18n}}" icon="fa-key"
|
||||||
*ngIf="clientSecret">
|
*ngIf="clientSecret">
|
||||||
<p class="mb-1">
|
<p class="mb-1">
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
</p>
|
</p>
|
||||||
<p class="mb-0">
|
<p class="mb-0">
|
||||||
<strong>grant_type:</strong><br>
|
<strong>grant_type:</strong><br>
|
||||||
<code>client_credentials</code>
|
<code>{{grantType}}</code>
|
||||||
</p>
|
</p>
|
||||||
</app-callout>
|
</app-callout>
|
||||||
</div>
|
</div>
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading"
|
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading"
|
||||||
*ngIf="!clientSecret">
|
*ngIf="!clientSecret">
|
||||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||||
<span>{{'viewApiKey' | i18n}}</span>
|
<span>{{(isRotation ? 'rotateApiKey' : 'viewApiKey') | i18n}}</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>
|
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>
|
||||||
</div>
|
</div>
|
|
@ -1,10 +1,8 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
|
||||||
|
|
||||||
import { ToasterService } from 'angular2-toaster';
|
import { ToasterService } from 'angular2-toaster';
|
||||||
import { Angulartics2 } from 'angulartics2';
|
import { Angulartics2 } from 'angulartics2';
|
||||||
|
|
||||||
import { ApiService } from 'jslib/abstractions/api.service';
|
|
||||||
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
|
|
||||||
|
@ -17,17 +15,23 @@ import { ApiKeyResponse } from 'jslib/models/response/apiKeyResponse';
|
||||||
templateUrl: 'api-key.component.html',
|
templateUrl: 'api-key.component.html',
|
||||||
})
|
})
|
||||||
export class ApiKeyComponent {
|
export class ApiKeyComponent {
|
||||||
organizationId: string;
|
keyType: string;
|
||||||
|
isRotation: boolean;
|
||||||
|
postKey: (entityId: string, request: PasswordVerificationRequest) => Promise<ApiKeyResponse>;
|
||||||
|
entityId: string;
|
||||||
|
scope: string;
|
||||||
|
grantType: string;
|
||||||
|
apiKeyTitle: string;
|
||||||
|
apiKeyWarning: string;
|
||||||
|
apiKeyDescription: string;
|
||||||
|
|
||||||
masterPassword: string;
|
masterPassword: string;
|
||||||
formPromise: Promise<ApiKeyResponse>;
|
formPromise: Promise<ApiKeyResponse>;
|
||||||
clientId: string;
|
clientId: string;
|
||||||
clientSecret: string;
|
clientSecret: string;
|
||||||
scope: string;
|
|
||||||
|
|
||||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
constructor(private i18nService: I18nService, private analytics: Angulartics2,
|
||||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
private toasterService: ToasterService, private cryptoService: CryptoService) { }
|
||||||
private cryptoService: CryptoService, private router: Router) { }
|
|
||||||
|
|
||||||
async submit() {
|
async submit() {
|
||||||
if (this.masterPassword == null || this.masterPassword === '') {
|
if (this.masterPassword == null || this.masterPassword === '') {
|
||||||
|
@ -39,12 +43,11 @@ export class ApiKeyComponent {
|
||||||
const request = new PasswordVerificationRequest();
|
const request = new PasswordVerificationRequest();
|
||||||
request.masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, null);
|
request.masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, null);
|
||||||
try {
|
try {
|
||||||
this.formPromise = this.apiService.postOrganizationApiKey(this.organizationId, request);
|
this.formPromise = this.postKey(this.entityId, request);
|
||||||
const response = await this.formPromise;
|
const response = await this.formPromise;
|
||||||
this.clientSecret = response.apiKey;
|
this.clientSecret = response.apiKey;
|
||||||
this.clientId = 'organization.' + this.organizationId;
|
this.clientId = `${this.keyType}.${this.entityId}`;
|
||||||
this.scope = 'api.organization';
|
this.analytics.eventTrack.next({ action: `Viewed ${this.keyType} API Key` });
|
||||||
this.analytics.eventTrack.next({ action: 'Viewed Organization API Key' });
|
|
||||||
} catch { }
|
} catch { }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2953,6 +2953,12 @@
|
||||||
"apiKeyWarning": {
|
"apiKeyWarning": {
|
||||||
"message": "Your API key has full access to the organization. It should be kept secret."
|
"message": "Your API key has full access to the organization. It should be kept secret."
|
||||||
},
|
},
|
||||||
|
"userApiKeyDesc": {
|
||||||
|
"message": "Your API key can be used to authenticate in the Bitwarden CLI."
|
||||||
|
},
|
||||||
|
"userApiKeyWarning": {
|
||||||
|
"message": "Your API key is an alternative authentication mechanism. It should be kept secret."
|
||||||
|
},
|
||||||
"oauth2ClientCredentials": {
|
"oauth2ClientCredentials": {
|
||||||
"message": "OAuth 2.0 Client Credentials",
|
"message": "OAuth 2.0 Client Credentials",
|
||||||
"description": "'OAuth 2.0' is a programming protocol. It should probably not be translated."
|
"description": "'OAuth 2.0' is a programming protocol. It should probably not be translated."
|
||||||
|
|
Loading…
Reference in New Issue