mirror of
https://github.com/bitwarden/browser.git
synced 2024-12-28 17:27:50 +01:00
get/rotate org api key
This commit is contained in:
parent
02ba2d3b60
commit
aabb1bc264
2
jslib
2
jslib
@ -1 +1 @@
|
||||
Subproject commit 0fa88b44b81730679fedf88a083b4b4b1f5c40ac
|
||||
Subproject commit 3b3b71d84192cc195f4626d6294b34d788641215
|
@ -55,9 +55,11 @@ import { UserGroupsComponent as OrgUserGroupsComponent } from './organizations/m
|
||||
|
||||
import { AccountComponent as OrgAccountComponent } from './organizations/settings/account.component';
|
||||
import { AdjustSeatsComponent } from './organizations/settings/adjust-seats.component';
|
||||
import { ApiKeyComponent as OrgApiKeyComponent } from './organizations/settings/api-key.component';
|
||||
import { DeleteOrganizationComponent } from './organizations/settings/delete-organization.component';
|
||||
import { OrganizationBillingComponent } from './organizations/settings/organization-billing.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 {
|
||||
TwoFactorSetupComponent as OrgTwoFactorSetupComponent,
|
||||
@ -274,6 +276,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
OptionsComponent,
|
||||
OrgAccountComponent,
|
||||
OrgAddEditComponent,
|
||||
OrgApiKeyComponent,
|
||||
OrganizationBillingComponent,
|
||||
OrganizationSubscriptionComponent,
|
||||
OrgAttachmentsComponent,
|
||||
@ -294,6 +297,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
OrgManageComponent,
|
||||
OrgPeopleComponent,
|
||||
OrgReusedPasswordsReportComponent,
|
||||
OrgRotateApiKeyComponent,
|
||||
OrgSettingComponent,
|
||||
OrgToolsComponent,
|
||||
OrgTwoFactorSetupComponent,
|
||||
@ -359,12 +363,14 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
FolderAddEditComponent,
|
||||
ModalComponent,
|
||||
OrgAddEditComponent,
|
||||
OrgApiKeyComponent,
|
||||
OrgAttachmentsComponent,
|
||||
OrgCollectionAddEditComponent,
|
||||
OrgCollectionsComponent,
|
||||
OrgEntityEventsComponent,
|
||||
OrgEntityUsersComponent,
|
||||
OrgGroupAddEditComponent,
|
||||
OrgRotateApiKeyComponent,
|
||||
OrgUserAddEditComponent,
|
||||
OrgUserConfirmComponent,
|
||||
OrgUserGroupsComponent,
|
||||
|
@ -31,6 +31,19 @@
|
||||
<span>{{'save' | i18n}}</span>
|
||||
</button>
|
||||
</form>
|
||||
<ng-container *ngIf="canUseApi">
|
||||
<div class="secondary-header border-0 mb-0">
|
||||
<h1>{{'apiKey' | i18n}}</h1>
|
||||
</div>
|
||||
<p>
|
||||
{{'apiKeyDesc' | i18n}}
|
||||
<a href="https://docs.bitwarden.com" target="_blank" rel="noopener">
|
||||
{{'learnMore' | i18n}}
|
||||
</a>
|
||||
</p>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="viewApiKey()">{{'viewApiKey' | i18n}}</button>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="rotateApiKey()">{{'rotateApiKey' | i18n}}</button>
|
||||
</ng-container>
|
||||
<div class="secondary-header border-0 mb-0">
|
||||
<h1>{{'taxInformation' | i18n}}</h1>
|
||||
</div>
|
||||
@ -51,3 +64,5 @@
|
||||
</div>
|
||||
<ng-template #deleteOrganizationTemplate></ng-template>
|
||||
<ng-template #purgeOrganizationTemplate></ng-template>
|
||||
<ng-template #apiKeyTemplate></ng-template>
|
||||
<ng-template #rotateApiKeyTemplate></ng-template>
|
||||
|
@ -18,7 +18,9 @@ import { OrganizationResponse } from 'jslib/models/response/organizationResponse
|
||||
|
||||
import { ModalComponent } from '../../modal.component';
|
||||
import { PurgeVaultComponent } from '../../settings/purge-vault.component';
|
||||
import { ApiKeyComponent } from './api-key.component';
|
||||
import { DeleteOrganizationComponent } from './delete-organization.component';
|
||||
import { RotateApiKeyComponent } from './rotate-api-key.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-org-account',
|
||||
@ -27,8 +29,11 @@ import { DeleteOrganizationComponent } from './delete-organization.component';
|
||||
export class AccountComponent {
|
||||
@ViewChild('deleteOrganizationTemplate', { read: ViewContainerRef }) deleteModalRef: ViewContainerRef;
|
||||
@ViewChild('purgeOrganizationTemplate', { read: ViewContainerRef }) purgeModalRef: ViewContainerRef;
|
||||
@ViewChild('apiKeyTemplate', { read: ViewContainerRef }) apiKeyModalRef: ViewContainerRef;
|
||||
@ViewChild('rotateApiKeyTemplate', { read: ViewContainerRef }) rotateApiKeyModalRef: ViewContainerRef;
|
||||
|
||||
loading = true;
|
||||
canUseApi = false;
|
||||
org: OrganizationResponse;
|
||||
formPromise: Promise<any>;
|
||||
|
||||
@ -45,6 +50,7 @@ export class AccountComponent {
|
||||
this.organizationId = params.organizationId;
|
||||
try {
|
||||
this.org = await this.apiService.getOrganization(this.organizationId);
|
||||
this.canUseApi = this.org.useApi;
|
||||
} catch { }
|
||||
});
|
||||
this.loading = false;
|
||||
@ -95,4 +101,34 @@ export class AccountComponent {
|
||||
this.modal = null;
|
||||
});
|
||||
}
|
||||
|
||||
viewApiKey() {
|
||||
if (this.modal != null) {
|
||||
this.modal.close();
|
||||
}
|
||||
|
||||
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
||||
this.modal = this.apiKeyModalRef.createComponent(factory).instance;
|
||||
const childComponent = this.modal.show<ApiKeyComponent>(ApiKeyComponent, this.apiKeyModalRef);
|
||||
childComponent.organizationId = this.organizationId;
|
||||
|
||||
this.modal.onClosed.subscribe(async () => {
|
||||
this.modal = null;
|
||||
});
|
||||
}
|
||||
|
||||
rotateApiKey() {
|
||||
if (this.modal != null) {
|
||||
this.modal.close();
|
||||
}
|
||||
|
||||
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
||||
this.modal = this.rotateApiKeyModalRef.createComponent(factory).instance;
|
||||
const childComponent = this.modal.show<RotateApiKeyComponent>(RotateApiKeyComponent, this.rotateApiKeyModalRef);
|
||||
childComponent.organizationId = this.organizationId;
|
||||
|
||||
this.modal.onClosed.subscribe(async () => {
|
||||
this.modal = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
48
src/app/organizations/settings/api-key.component.html
Normal file
48
src/app/organizations/settings/api-key.component.html
Normal file
@ -0,0 +1,48 @@
|
||||
<div class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">{{'apiKey' | i18n}}</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{'apiKeyDesc' | 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}}"></i>
|
||||
<span>{{'viewApiKey' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
50
src/app/organizations/settings/api-key.component.ts
Normal file
50
src/app/organizations/settings/api-key.component.ts
Normal file
@ -0,0 +1,50 @@
|
||||
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-api-key',
|
||||
templateUrl: 'api-key.component.html',
|
||||
})
|
||||
export class ApiKeyComponent {
|
||||
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.postOrganizationApiKey(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: 'Viewed Organization API Key' });
|
||||
} catch { }
|
||||
}
|
||||
}
|
48
src/app/organizations/settings/rotate-api-key.component.html
Normal file
48
src/app/organizations/settings/rotate-api-key.component.html
Normal file
@ -0,0 +1,48 @@
|
||||
<div class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">{{'rotateApiKey' | i18n}}</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'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}}"></i>
|
||||
<span>{{'rotateApiKey' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
50
src/app/organizations/settings/rotate-api-key.component.ts
Normal file
50
src/app/organizations/settings/rotate-api-key.component.ts
Normal file
@ -0,0 +1,50 @@
|
||||
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 { }
|
||||
}
|
||||
}
|
@ -2795,5 +2795,27 @@
|
||||
"free": {
|
||||
"message": "Free",
|
||||
"description": "Free, as in 'Free beer'"
|
||||
},
|
||||
"apiKey": {
|
||||
"message": "API Key"
|
||||
},
|
||||
"apiKeyDesc": {
|
||||
"message": "Your API key can be used to authenticate to the Bitwarden public API."
|
||||
},
|
||||
"apiKeyRotateDesc": {
|
||||
"message": "Rotating the API key will invalidate the previous key. You can rotate your API key if you believe that the current key is no longer safe to use."
|
||||
},
|
||||
"apiKeyWarning": {
|
||||
"message": "Your API key has full access to the organization. It should be kept secret."
|
||||
},
|
||||
"oauth2ClientCredentials": {
|
||||
"message": "OAuth 2.0 Client Credentials",
|
||||
"description": "'OAuth 2.0' is a programming protocol. It should probably not be translated."
|
||||
},
|
||||
"viewApiKey": {
|
||||
"message": "View API Key"
|
||||
},
|
||||
"rotateApiKey": {
|
||||
"message": "Rotate API Key"
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user