1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-12-21 16:18:28 +01:00

[SM-501] Revoke Access Tokens (#4746)

* Add support for revoking access tokens
This commit is contained in:
Oscar Hinton 2023-02-16 10:51:11 +01:00 committed by GitHub
parent d269439391
commit 55741445ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 83 additions and 4 deletions

View File

@ -5952,6 +5952,16 @@
"revokeAccessToken": {
"message": "Revoke access token"
},
"revokeAccessTokens": {
"message": "Revoke access tokens"
},
"revokeAccessTokenDesc": {
"message": "Revoking access tokens is permanent and irreversible."
},
"accessTokenRevoked": {
"message": "Access tokens revoked",
"description": "Toast message after deleting one or multiple access tokens."
},
"submenu": {
"message": "Submenu"
},

View File

@ -40,6 +40,7 @@
type="button"
bitIconButton="bwi-ellipsis-v"
buttonType="main"
[bitMenuTriggerFor]="tableMenu"
[title]="'options' | i18n"
[attr.aria-label]="'options' | i18n"
></button>
@ -73,7 +74,7 @@
</td>
<bit-menu #tokenMenu>
<button type="button" bitMenuItem>
<button type="button" bitMenuItem (click)="revokeAccessTokensEvent.emit([token])">
<span class="tw-text-danger">
<i class="bwi bwi-fw bwi-minus-circle" aria-hidden="true"></i>
{{ "revokeAccessToken" | i18n }}
@ -83,3 +84,10 @@
</tr>
</ng-template>
</bit-table>
<bit-menu #tableMenu>
<button type="button" bitMenuItem (click)="revokeSelected()">
<i class="bwi bwi-fw bwi-minus-circle" aria-hidden="true"></i>
<span class="tw-text-danger">{{ "revokeAccessTokens" | i18n }}</span>
</button>
</bit-menu>

View File

@ -19,6 +19,7 @@ export class AccessListComponent {
private _tokens: AccessTokenView[];
@Output() newAccessTokenEvent = new EventEmitter();
@Output() revokeAccessTokensEvent = new EventEmitter<AccessTokenView[]>();
protected selection = new SelectionModel<string>(true, []);
@ -34,6 +35,11 @@ export class AccessListComponent {
: this.selection.select(...this.tokens.map((s) => s.id));
}
protected revokeSelected() {
const selected = this.tokens.filter((s) => this.selection.selected.includes(s.id));
this.revokeAccessTokensEvent.emit(selected);
}
protected permission(token: AccessTokenView) {
return "canRead";
}

View File

@ -1,4 +1,5 @@
<sm-access-list
[tokens]="accessTokens$ | async"
(newAccessTokenEvent)="openNewAccessTokenDialog()"
(revokeAccessTokensEvent)="revoke($event)"
></sm-access-list>

View File

@ -2,7 +2,10 @@ import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { combineLatestWith, Observable, startWith, switchMap } from "rxjs";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { DialogService } from "@bitwarden/components";
import { UserVerificationPromptComponent } from "@bitwarden/web-vault/app/components/user-verification-prompt.component";
import { AccessTokenView } from "../models/view/access-token.view";
@ -22,7 +25,9 @@ export class AccessTokenComponent implements OnInit {
constructor(
private route: ActivatedRoute,
private accessService: AccessService,
private dialogService: DialogService
private dialogService: DialogService,
private modalService: ModalService,
private platformUtilsService: PlatformUtilsService
) {}
ngOnInit() {
@ -37,8 +42,17 @@ export class AccessTokenComponent implements OnInit {
);
}
private async getAccessTokens(): Promise<AccessTokenView[]> {
return await this.accessService.getAccessTokens(this.organizationId, this.serviceAccountId);
protected async revoke(tokens: AccessTokenView[]) {
if (!(await this.verifyUser())) {
return;
}
await this.accessService.revokeAccessTokens(
this.serviceAccountId,
tokens.map((t) => t.id)
);
this.platformUtilsService.showToast("success", null, "Access tokens revoked.");
}
protected openNewAccessTokenDialog() {
@ -48,4 +62,25 @@ export class AccessTokenComponent implements OnInit {
this.organizationId
);
}
private verifyUser() {
const ref = this.modalService.open(UserVerificationPromptComponent, {
allowMultipleModals: true,
data: {
confirmDescription: "revokeAccessTokenDesc",
confirmButtonText: "revokeAccessToken",
modalTitle: "revokeAccessToken",
},
});
if (ref == null) {
return;
}
return ref.onClosedPromise();
}
private async getAccessTokens(): Promise<AccessTokenView[]> {
return await this.accessService.getAccessTokens(this.organizationId, this.serviceAccountId);
}
}

View File

@ -11,6 +11,7 @@ import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-cr
import { ListResponse } from "@bitwarden/common/models/response/list.response";
import { AccessTokenRequest } from "../models/requests/access-token.request";
import { RevokeAccessTokensRequest } from "../models/requests/revoke-access-tokens.request";
import { AccessTokenCreationResponse } from "../models/responses/access-token-creation.response";
import { AccessTokenResponse } from "../models/responses/access-tokens.response";
import { AccessTokenView } from "../models/view/access-token.view";
@ -80,6 +81,21 @@ export class AccessService {
return `${this._accessTokenVersion}.${result.id}.${result.clientSecret}:${b64Key}`;
}
async revokeAccessTokens(serviceAccountId: string, accessTokenIds: string[]): Promise<void> {
const request = new RevokeAccessTokensRequest();
request.ids = accessTokenIds;
await this.apiService.send(
"POST",
"/service-accounts/" + serviceAccountId + "/access-tokens/revoke",
request,
true,
false
);
this._accessToken.next(null);
}
private async createAccessTokenRequest(
organizationId: string,
encryptionKey: SymmetricCryptoKey,

View File

@ -0,0 +1,3 @@
export class RevokeAccessTokensRequest {
ids: string[];
}