mirror of
https://github.com/bitwarden/browser.git
synced 2025-01-02 18:17:46 +01:00
[SM-501] Revoke Access Tokens (#4746)
* Add support for revoking access tokens
This commit is contained in:
parent
d269439391
commit
55741445ec
@ -5952,6 +5952,16 @@
|
|||||||
"revokeAccessToken": {
|
"revokeAccessToken": {
|
||||||
"message": "Revoke access token"
|
"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": {
|
"submenu": {
|
||||||
"message": "Submenu"
|
"message": "Submenu"
|
||||||
},
|
},
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
type="button"
|
type="button"
|
||||||
bitIconButton="bwi-ellipsis-v"
|
bitIconButton="bwi-ellipsis-v"
|
||||||
buttonType="main"
|
buttonType="main"
|
||||||
|
[bitMenuTriggerFor]="tableMenu"
|
||||||
[title]="'options' | i18n"
|
[title]="'options' | i18n"
|
||||||
[attr.aria-label]="'options' | i18n"
|
[attr.aria-label]="'options' | i18n"
|
||||||
></button>
|
></button>
|
||||||
@ -73,7 +74,7 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<bit-menu #tokenMenu>
|
<bit-menu #tokenMenu>
|
||||||
<button type="button" bitMenuItem>
|
<button type="button" bitMenuItem (click)="revokeAccessTokensEvent.emit([token])">
|
||||||
<span class="tw-text-danger">
|
<span class="tw-text-danger">
|
||||||
<i class="bwi bwi-fw bwi-minus-circle" aria-hidden="true"></i>
|
<i class="bwi bwi-fw bwi-minus-circle" aria-hidden="true"></i>
|
||||||
{{ "revokeAccessToken" | i18n }}
|
{{ "revokeAccessToken" | i18n }}
|
||||||
@ -83,3 +84,10 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</bit-table>
|
</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>
|
||||||
|
@ -19,6 +19,7 @@ export class AccessListComponent {
|
|||||||
private _tokens: AccessTokenView[];
|
private _tokens: AccessTokenView[];
|
||||||
|
|
||||||
@Output() newAccessTokenEvent = new EventEmitter();
|
@Output() newAccessTokenEvent = new EventEmitter();
|
||||||
|
@Output() revokeAccessTokensEvent = new EventEmitter<AccessTokenView[]>();
|
||||||
|
|
||||||
protected selection = new SelectionModel<string>(true, []);
|
protected selection = new SelectionModel<string>(true, []);
|
||||||
|
|
||||||
@ -34,6 +35,11 @@ export class AccessListComponent {
|
|||||||
: this.selection.select(...this.tokens.map((s) => s.id));
|
: 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) {
|
protected permission(token: AccessTokenView) {
|
||||||
return "canRead";
|
return "canRead";
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<sm-access-list
|
<sm-access-list
|
||||||
[tokens]="accessTokens$ | async"
|
[tokens]="accessTokens$ | async"
|
||||||
(newAccessTokenEvent)="openNewAccessTokenDialog()"
|
(newAccessTokenEvent)="openNewAccessTokenDialog()"
|
||||||
|
(revokeAccessTokensEvent)="revoke($event)"
|
||||||
></sm-access-list>
|
></sm-access-list>
|
||||||
|
@ -2,7 +2,10 @@ import { Component, OnInit } from "@angular/core";
|
|||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import { combineLatestWith, Observable, startWith, switchMap } from "rxjs";
|
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 { DialogService } from "@bitwarden/components";
|
||||||
|
import { UserVerificationPromptComponent } from "@bitwarden/web-vault/app/components/user-verification-prompt.component";
|
||||||
|
|
||||||
import { AccessTokenView } from "../models/view/access-token.view";
|
import { AccessTokenView } from "../models/view/access-token.view";
|
||||||
|
|
||||||
@ -22,7 +25,9 @@ export class AccessTokenComponent implements OnInit {
|
|||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private accessService: AccessService,
|
private accessService: AccessService,
|
||||||
private dialogService: DialogService
|
private dialogService: DialogService,
|
||||||
|
private modalService: ModalService,
|
||||||
|
private platformUtilsService: PlatformUtilsService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@ -37,8 +42,17 @@ export class AccessTokenComponent implements OnInit {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getAccessTokens(): Promise<AccessTokenView[]> {
|
protected async revoke(tokens: AccessTokenView[]) {
|
||||||
return await this.accessService.getAccessTokens(this.organizationId, this.serviceAccountId);
|
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() {
|
protected openNewAccessTokenDialog() {
|
||||||
@ -48,4 +62,25 @@ export class AccessTokenComponent implements OnInit {
|
|||||||
this.organizationId
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-cr
|
|||||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||||
|
|
||||||
import { AccessTokenRequest } from "../models/requests/access-token.request";
|
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 { AccessTokenCreationResponse } from "../models/responses/access-token-creation.response";
|
||||||
import { AccessTokenResponse } from "../models/responses/access-tokens.response";
|
import { AccessTokenResponse } from "../models/responses/access-tokens.response";
|
||||||
import { AccessTokenView } from "../models/view/access-token.view";
|
import { AccessTokenView } from "../models/view/access-token.view";
|
||||||
@ -80,6 +81,21 @@ export class AccessService {
|
|||||||
return `${this._accessTokenVersion}.${result.id}.${result.clientSecret}:${b64Key}`;
|
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(
|
private async createAccessTokenRequest(
|
||||||
organizationId: string,
|
organizationId: string,
|
||||||
encryptionKey: SymmetricCryptoKey,
|
encryptionKey: SymmetricCryptoKey,
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
export class RevokeAccessTokensRequest {
|
||||||
|
ids: string[];
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user