mirror of
https://github.com/turt2live/matrix-dimension.git
synced 2024-10-01 01:05:53 -04:00
Merge branch 'travis/token-security'
This commit is contained in:
commit
0c0429df46
@ -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 config from "../../config";
|
||||
import { ApiError } from "../ApiError";
|
||||
import { MatrixLiteClient } from "../../matrix/MatrixLiteClient";
|
||||
import { CURRENT_VERSION } from "../../version";
|
||||
import { getFederationConnInfo } from "../../matrix/helpers";
|
||||
import UserScalarToken from "../../db/models/UserScalarToken";
|
||||
import { Cache, CACHE_SCALAR_ACCOUNTS } from "../../MemoryCache";
|
||||
|
||||
interface DimensionVersionResponse {
|
||||
version: string;
|
||||
@ -20,6 +22,9 @@ interface DimensionConfigResponse {
|
||||
federationHostname: string;
|
||||
clientServerUrl: string;
|
||||
};
|
||||
sessionInfo: {
|
||||
numTokens: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,6 +87,9 @@ export class AdminService {
|
||||
federationHostname: fedInfo.hostname,
|
||||
clientServerUrl: config.homeserver.clientServerUrl,
|
||||
},
|
||||
sessionInfo: {
|
||||
numTokens: await UserScalarToken.count(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -95,4 +103,23 @@ export class AdminService {
|
||||
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 {};
|
||||
}
|
||||
}
|
@ -49,7 +49,11 @@ export class ScalarService {
|
||||
|
||||
@POST
|
||||
@Path("register")
|
||||
public async register(request: RegisterRequest): Promise<ScalarRegisterResponse> {
|
||||
public async register(request: RegisterRequest, @QueryParam("v") apiVersion: string): Promise<ScalarRegisterResponse> {
|
||||
if (apiVersion !== "1.1") {
|
||||
throw new ApiError(401, "Invalid API version.");
|
||||
}
|
||||
|
||||
const mxClient = new MatrixOpenIdClient(<OpenId>request);
|
||||
const mxUserId = await mxClient.getUserId();
|
||||
|
||||
@ -95,7 +99,11 @@ export class ScalarService {
|
||||
|
||||
@GET
|
||||
@Path("account")
|
||||
public async getAccount(@QueryParam("scalar_token") scalarToken: string): Promise<ScalarAccountResponse> {
|
||||
public async getAccount(@QueryParam("scalar_token") scalarToken: string, @QueryParam("v") apiVersion: string): Promise<ScalarAccountResponse> {
|
||||
if (apiVersion !== "1.1") {
|
||||
throw new ApiError(401, "Invalid API version.");
|
||||
}
|
||||
|
||||
const userId = await ScalarService.getTokenOwner(scalarToken);
|
||||
return {user_id: userId};
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import { ScalarRegisterResponse } from "../models/ScalarResponses";
|
||||
import * as request from "request";
|
||||
import { LogService } from "matrix-js-snippets";
|
||||
import Upstream from "../db/models/Upstream";
|
||||
import { SCALAR_API_VERSION } from "../utils/common-constants";
|
||||
|
||||
export class ScalarClient {
|
||||
constructor(private upstream: Upstream) {
|
||||
@ -14,6 +15,7 @@ export class ScalarClient {
|
||||
request({
|
||||
method: "POST",
|
||||
url: this.upstream.scalarUrl + "/register",
|
||||
qs: {v: SCALAR_API_VERSION},
|
||||
json: openId,
|
||||
}, (err, res, _body) => {
|
||||
if (err) {
|
||||
|
1
src/utils/common-constants.ts
Normal file
1
src/utils/common-constants.ts
Normal file
@ -0,0 +1 @@
|
||||
export const SCALAR_API_VERSION = "1.1";
|
@ -28,6 +28,16 @@
|
||||
Utility User ID: {{ config.homeserver.userId }}
|
||||
</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>
|
||||
</my-ibox>
|
||||
</div>
|
@ -1,6 +1,12 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { AdminApiService } from "../../shared/services/admin/admin-api.service";
|
||||
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({
|
||||
templateUrl: "./home.component.html",
|
||||
@ -11,10 +17,26 @@ export class AdminHomeComponent {
|
||||
public isLoading = true;
|
||||
public config: FE_DimensionConfig;
|
||||
|
||||
constructor(adminApi: AdminApiService) {
|
||||
constructor(private adminApi: AdminApiService,
|
||||
private toaster: ToasterService,
|
||||
private modal: Modal) {
|
||||
adminApi.getConfig().then(config => {
|
||||
this.config = config;
|
||||
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");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
@ -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>) {}
|
||||
}
|
@ -109,6 +109,7 @@ import { SlackBridgeConfigComponent } from "./configs/bridge/slack/slack.bridge.
|
||||
import { AdminSlackBridgeManageSelfhostedComponent } from "./admin/bridges/slack/manage-selfhosted/manage-selfhosted.component";
|
||||
import { AdminSlackBridgeComponent } from "./admin/bridges/slack/slack.component";
|
||||
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 { ManagerTestWidgetWrapperComponent } from "./widget-wrappers/manager-test/manager-test.component";
|
||||
|
||||
@ -200,6 +201,7 @@ import { ManagerTestWidgetWrapperComponent } from "./widget-wrappers/manager-tes
|
||||
SlackBridgeConfigComponent,
|
||||
AdminSlackBridgeManageSelfhostedComponent,
|
||||
AdminSlackBridgeComponent,
|
||||
AdminLogoutConfirmationDialogComponent,
|
||||
ReauthExampleWidgetWrapperComponent,
|
||||
ManagerTestWidgetWrapperComponent,
|
||||
|
||||
@ -254,6 +256,7 @@ import { ManagerTestWidgetWrapperComponent } from "./widget-wrappers/manager-tes
|
||||
AdminGitterBridgeManageSelfhostedComponent,
|
||||
AdminAddCustomBotComponent,
|
||||
AdminSlackBridgeManageSelfhostedComponent,
|
||||
AdminLogoutConfirmationDialogComponent,
|
||||
]
|
||||
})
|
||||
export class AppModule {
|
||||
|
@ -10,6 +10,9 @@ export interface FE_DimensionConfig {
|
||||
federationHostname: string;
|
||||
clientServerUrl: string;
|
||||
};
|
||||
sessionInfo: {
|
||||
numTokens: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface FE_DimensionVersion {
|
||||
|
@ -20,4 +20,8 @@ export class AdminApiService extends AuthedApi {
|
||||
public getVersion(): Promise<FE_DimensionVersion> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
FE_ScalarRegisterResponse
|
||||
} from "../../models/scalar-server-responses";
|
||||
import { AuthedApi } from "../authed-api";
|
||||
import { SCALAR_API_VERSION } from "../../../../../src/utils/common-constants";
|
||||
|
||||
@Injectable()
|
||||
export class ScalarServerApiService extends AuthedApi {
|
||||
@ -18,10 +19,12 @@ export class ScalarServerApiService extends AuthedApi {
|
||||
}
|
||||
|
||||
public getAccount(): Promise<FE_ScalarAccountResponse> {
|
||||
return this.authedGet("/api/v1/scalar/account").map(res => res.json()).toPromise();
|
||||
return this.authedGet("/api/v1/scalar/account", {v: SCALAR_API_VERSION}).map(res => res.json()).toPromise();
|
||||
}
|
||||
|
||||
public register(openId: FE_ScalarOpenIdRequestBody): Promise<FE_ScalarRegisterResponse> {
|
||||
return this.http.post("/api/v1/scalar/register", openId).map(res => res.json()).toPromise();
|
||||
return this.http.post("/api/v1/scalar/register", openId, {
|
||||
params: {v: SCALAR_API_VERSION},
|
||||
}).map(res => res.json()).toPromise();
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user