Add a button to log everyone out

This commit is contained in:
Travis Ralston 2019-03-12 19:08:12 -06:00
parent 70c5471df7
commit 0287e472f8
9 changed files with 106 additions and 2 deletions

View File

@ -1,10 +1,12 @@
import { GET, Path, QueryParam } from "typescript-rest"; import { GET, Path, POST, QueryParam } from "typescript-rest";
import { ScalarService } from "../scalar/ScalarService"; import { ScalarService } from "../scalar/ScalarService";
import config from "../../config"; import config from "../../config";
import { ApiError } from "../ApiError"; import { ApiError } from "../ApiError";
import { MatrixLiteClient } from "../../matrix/MatrixLiteClient"; import { MatrixLiteClient } from "../../matrix/MatrixLiteClient";
import { CURRENT_VERSION } from "../../version"; import { CURRENT_VERSION } from "../../version";
import { getFederationConnInfo } from "../../matrix/helpers"; import { getFederationConnInfo } from "../../matrix/helpers";
import UserScalarToken from "../../db/models/UserScalarToken";
import { Cache, CACHE_SCALAR_ACCOUNTS } from "../../MemoryCache";
interface DimensionVersionResponse { interface DimensionVersionResponse {
version: string; version: string;
@ -20,6 +22,9 @@ interface DimensionConfigResponse {
federationHostname: string; federationHostname: string;
clientServerUrl: string; clientServerUrl: string;
}; };
sessionInfo: {
numTokens: number;
};
} }
/** /**
@ -82,6 +87,9 @@ export class AdminService {
federationHostname: fedInfo.hostname, federationHostname: fedInfo.hostname,
clientServerUrl: config.homeserver.clientServerUrl, clientServerUrl: config.homeserver.clientServerUrl,
}, },
sessionInfo: {
numTokens: await UserScalarToken.count(),
},
}; };
} }
@ -95,4 +103,23 @@ export class AdminService {
resolvedServer: await getFederationConnInfo(serverName), resolvedServer: await getFederationConnInfo(serverName),
}; };
} }
@POST
@Path("sessions/logout/all")
public async logoutAll(@QueryParam("scalar_token") scalarToken: string): Promise<any> {
await AdminService.validateAndGetAdminTokenOwner(scalarToken);
// Clear the cache first to hopefully invalidate a bunch of them
Cache.for(CACHE_SCALAR_ACCOUNTS).clear();
const tokens = await UserScalarToken.all();
for (const token of tokens) {
await token.destroy();
}
// Clear it again because the delete loop can be slow
Cache.for(CACHE_SCALAR_ACCOUNTS).clear();
return {};
}
} }

View File

@ -28,6 +28,16 @@
Utility User ID: {{ config.homeserver.userId }} Utility User ID: {{ config.homeserver.userId }}
</div> </div>
</div> </div>
<div class="row">
<div class="col-md-4">
<strong>Sessions</strong><br />
Tokens registered: {{ config.sessionInfo.numTokens }}<br />
<button class="btn btn-danger btn-sm" type="button" (click)="logoutAll()">
Logout Everyone
</button>
</div>
</div>
</div> </div>
</my-ibox> </my-ibox>
</div> </div>

View File

@ -1,6 +1,12 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { AdminApiService } from "../../shared/services/admin/admin-api.service"; import { AdminApiService } from "../../shared/services/admin/admin-api.service";
import { FE_DimensionConfig } from "../../shared/models/admin-responses"; import { FE_DimensionConfig } from "../../shared/models/admin-responses";
import { ToasterService } from "angular2-toaster";
import { Modal, overlayConfigFactory } from "ngx-modialog";
import {
AdminLogoutConfirmationDialogComponent,
LogoutConfirmationDialogContext
} from "./logout-confirmation/logout-confirmation.component";
@Component({ @Component({
templateUrl: "./home.component.html", templateUrl: "./home.component.html",
@ -11,10 +17,26 @@ export class AdminHomeComponent {
public isLoading = true; public isLoading = true;
public config: FE_DimensionConfig; public config: FE_DimensionConfig;
constructor(adminApi: AdminApiService) { constructor(private adminApi: AdminApiService,
private toaster: ToasterService,
private modal: Modal) {
adminApi.getConfig().then(config => { adminApi.getConfig().then(config => {
this.config = config; this.config = config;
this.isLoading = false; this.isLoading = false;
}); });
} }
public logoutAll(): void {
this.modal.open(AdminLogoutConfirmationDialogComponent, overlayConfigFactory({
isBlocking: true,
}, LogoutConfirmationDialogContext)).result.then(() => {
this.adminApi.logoutAll().then(() => {
this.toaster.pop("success", "Everyone has been logged out");
this.config.sessionInfo.numTokens = 0;
}).catch(err => {
console.error(err);
this.toaster.pop("error", "Error logging everyone out");
});
});
}
} }

View File

@ -0,0 +1,20 @@
<div class="dialog">
<div class="dialog-header">
<h4>Logout confirmation</h4>
</div>
<div class="dialog-content">
<p>
Logging everyone out will disable all known login tokens for Dimension and upstream integration managers.
Most clients will automatically re-register for a login token behind the scenes, similar to how a login token
was first acquired.
</p>
</div>
<div class="dialog-footer">
<button type="button" (click)="dialog.dismiss()" title="close" class="btn btn-secondary btn-sm">
<i class="far fa-times-circle"></i> Cancel
</button>
<button type="button" (click)="dialog.close()" title="logout everyone" class="btn btn-danger btn-sm">
<i class="far fa-times-circle"></i> Logout Everyone
</button>
</div>
</div>

View File

@ -0,0 +1,15 @@
import { Component } from "@angular/core";
import { DialogRef, ModalComponent } from "ngx-modialog";
import { BSModalContext } from "ngx-modialog/plugins/bootstrap";
export class LogoutConfirmationDialogContext extends BSModalContext {
}
@Component({
templateUrl: "./logout-confirmation.component.html",
styleUrls: ["./logout-confirmation.component.scss"],
})
export class AdminLogoutConfirmationDialogComponent implements ModalComponent<LogoutConfirmationDialogContext> {
constructor(public dialog: DialogRef<LogoutConfirmationDialogContext>) {}
}

View File

@ -109,6 +109,7 @@ import { SlackBridgeConfigComponent } from "./configs/bridge/slack/slack.bridge.
import { AdminSlackBridgeManageSelfhostedComponent } from "./admin/bridges/slack/manage-selfhosted/manage-selfhosted.component"; import { AdminSlackBridgeManageSelfhostedComponent } from "./admin/bridges/slack/manage-selfhosted/manage-selfhosted.component";
import { AdminSlackBridgeComponent } from "./admin/bridges/slack/slack.component"; import { AdminSlackBridgeComponent } from "./admin/bridges/slack/slack.component";
import { AdminSlackApiService } from "./shared/services/admin/admin-slack-api.service"; import { AdminSlackApiService } from "./shared/services/admin/admin-slack-api.service";
import { AdminLogoutConfirmationDialogComponent } from "./admin/home/logout-confirmation/logout-confirmation.component";
import { ReauthExampleWidgetWrapperComponent } from "./widget-wrappers/reauth-example/reauth-example.component"; import { ReauthExampleWidgetWrapperComponent } from "./widget-wrappers/reauth-example/reauth-example.component";
@NgModule({ @NgModule({
@ -199,6 +200,7 @@ import { ReauthExampleWidgetWrapperComponent } from "./widget-wrappers/reauth-ex
SlackBridgeConfigComponent, SlackBridgeConfigComponent,
AdminSlackBridgeManageSelfhostedComponent, AdminSlackBridgeManageSelfhostedComponent,
AdminSlackBridgeComponent, AdminSlackBridgeComponent,
AdminLogoutConfirmationDialogComponent,
ReauthExampleWidgetWrapperComponent, ReauthExampleWidgetWrapperComponent,
// Vendor // Vendor
@ -252,6 +254,7 @@ import { ReauthExampleWidgetWrapperComponent } from "./widget-wrappers/reauth-ex
AdminGitterBridgeManageSelfhostedComponent, AdminGitterBridgeManageSelfhostedComponent,
AdminAddCustomBotComponent, AdminAddCustomBotComponent,
AdminSlackBridgeManageSelfhostedComponent, AdminSlackBridgeManageSelfhostedComponent,
AdminLogoutConfirmationDialogComponent,
] ]
}) })
export class AppModule { export class AppModule {

View File

@ -10,6 +10,9 @@ export interface FE_DimensionConfig {
federationHostname: string; federationHostname: string;
clientServerUrl: string; clientServerUrl: string;
}; };
sessionInfo: {
numTokens: number;
};
} }
export interface FE_DimensionVersion { export interface FE_DimensionVersion {

View File

@ -20,4 +20,8 @@ export class AdminApiService extends AuthedApi {
public getVersion(): Promise<FE_DimensionVersion> { public getVersion(): Promise<FE_DimensionVersion> {
return this.authedGet("/api/v1/dimension/admin/version").map(r => r.json()).toPromise(); return this.authedGet("/api/v1/dimension/admin/version").map(r => r.json()).toPromise();
} }
public logoutAll(): Promise<any> {
return this.authedPost("/api/v1/dimension/admin/sessions/logout/all").map(r => r.json()).toPromise();
}
} }