16223 user auth token list and revoke (#17434)
* Start user auth token list on user profile (#16223) * Now session found and first, better os regex (#16223) * Revoke for user and admin user token manage (#16223) * Tidying and styling (#16223) * Tidying and styling (#16223) * Update to use #16222 (#16223) * Update for changes to 16222 * update per api issue
This commit is contained in:
@@ -126,7 +126,7 @@ export class SharedPreferences extends PureComponent<Props, State> {
|
||||
getOptionLabel={i => i.title}
|
||||
onChange={(dashboard: DashboardSearchHit) => this.onHomeDashboardChanged(dashboard.id)}
|
||||
options={dashboards}
|
||||
placeholder="Chose default dashboard"
|
||||
placeholder="Choose default dashboard"
|
||||
width={20}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import _ from 'lodash';
|
||||
import { dateTime } from '@grafana/ui';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { NavModelSrv } from 'app/core/core';
|
||||
import { User } from 'app/core/services/context_srv';
|
||||
import { UserSession } from 'app/types';
|
||||
|
||||
export default class AdminEditUserCtrl {
|
||||
/** @ngInject */
|
||||
constructor($scope: any, $routeParams: any, backendSrv: BackendSrv, $location: any, navModelSrv: NavModelSrv) {
|
||||
$scope.user = {};
|
||||
$scope.sessions = [];
|
||||
$scope.newOrg = { name: '', role: 'Editor' };
|
||||
$scope.permissions = {};
|
||||
$scope.navModel = navModelSrv.getNav('admin', 'global-users', 0);
|
||||
@@ -14,6 +17,7 @@ export default class AdminEditUserCtrl {
|
||||
$scope.init = () => {
|
||||
if ($routeParams.id) {
|
||||
$scope.getUser($routeParams.id);
|
||||
$scope.getUserSessions($routeParams.id);
|
||||
$scope.getUserOrgs($routeParams.id);
|
||||
}
|
||||
};
|
||||
@@ -26,6 +30,48 @@ export default class AdminEditUserCtrl {
|
||||
});
|
||||
};
|
||||
|
||||
$scope.getUserSessions = (id: number) => {
|
||||
backendSrv.get('/api/admin/users/' + id + '/auth-tokens').then((sessions: UserSession[]) => {
|
||||
sessions.reverse();
|
||||
|
||||
$scope.sessions = sessions.map((session: UserSession) => {
|
||||
return {
|
||||
id: session.id,
|
||||
isActive: session.isActive,
|
||||
seenAt: dateTime(session.seenAt).fromNow(),
|
||||
createdAt: dateTime(session.createdAt).format('MMMM DD, YYYY'),
|
||||
clientIp: session.clientIp,
|
||||
browser: session.browser,
|
||||
browserVersion: session.browserVersion,
|
||||
os: session.os,
|
||||
osVersion: session.osVersion,
|
||||
device: session.device,
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.revokeUserSession = (tokenId: number) => {
|
||||
backendSrv
|
||||
.post('/api/admin/users/' + $scope.user_id + '/revoke-auth-token', {
|
||||
authTokenId: tokenId,
|
||||
})
|
||||
.then(() => {
|
||||
$scope.sessions = $scope.sessions.filter((session: UserSession) => {
|
||||
if (session.id === tokenId) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.revokeAllUserSessions = (tokenId: number) => {
|
||||
backendSrv.post('/api/admin/users/' + $scope.user_id + '/logout').then(() => {
|
||||
$scope.sessions = [];
|
||||
});
|
||||
};
|
||||
|
||||
$scope.setPassword = () => {
|
||||
if (!$scope.passwordForm.$valid) {
|
||||
return;
|
||||
|
||||
@@ -70,32 +70,66 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<table class="filter-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Role</th>
|
||||
<th></th>
|
||||
<div class="gf-form-group">
|
||||
<table class="filter-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Role</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr ng-repeat="org in orgs">
|
||||
<td>
|
||||
{{org.name}} <span class="label label-info" ng-show="org.orgId === user.orgId">Current</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-select-wrapper">
|
||||
<select type="text" ng-model="org.role" class="gf-form-input max-width-12" ng-options="f for f in ['Viewer', 'Editor', 'Admin']" ng-change="updateOrgUser(org)">
|
||||
</select>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td style="width: 1%">
|
||||
<a ng-click="removeOrgUser(org)" class="btn btn-danger btn-small">
|
||||
<i class="fa fa-remove"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h3 class="page-heading">Sessions</h3>
|
||||
|
||||
<div class="gf-form-group">
|
||||
<table class="filter-table form-inline">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Last seen</th>
|
||||
<th>Logged on</th>
|
||||
<th>IP address</th>
|
||||
<th>Browser & OS</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr ng-repeat="org in orgs">
|
||||
<tbody>
|
||||
<tr ng-repeat="session in sessions">
|
||||
<td ng-if="session.isActive">Now</td>
|
||||
<td ng-if="!session.isActive">{{session.seenAt}}</td>
|
||||
<td>{{session.createdAt}}</td>
|
||||
<td>{{session.clientIp}}</td>
|
||||
<td>{{session.browser}} on {{session.os}} {{session.osVersion}}</td>
|
||||
<td>
|
||||
{{org.name}} <span class="label label-info" ng-show="org.orgId === user.orgId">Current</span>
|
||||
<button class="btn btn-danger btn-small" ng-click="revokeUserSession(session.id)">
|
||||
<i class="fa fa-power-off"></i>
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-select-wrapper">
|
||||
<select type="text" ng-model="org.role" class="gf-form-input max-width-12" ng-options="f for f in ['Viewer', 'Editor', 'Admin']" ng-change="updateOrgUser(org)">
|
||||
</select>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td style="width: 1%">
|
||||
<a ng-click="removeOrgUser(org)" class="btn btn-danger btn-small">
|
||||
<i class="fa fa-remove"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<button ng-if="sessions.length" class="btn btn-danger" ng-click="revokeAllUserSessions()">Logout user from all devices</button>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -13,9 +13,7 @@ function convertToAlertRule(dto: AlertRuleDTO, state: string): AlertRule {
|
||||
stateText: stateModel.text,
|
||||
stateIcon: stateModel.iconClass,
|
||||
stateClass: stateModel.stateClass,
|
||||
stateAge: dateTime(dto.newStateDate)
|
||||
.fromNow()
|
||||
.replace(' ago', ''),
|
||||
stateAge: dateTime(dto.newStateDate).fromNow(true),
|
||||
};
|
||||
|
||||
if (rule.state !== 'paused') {
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import config from 'app/core/config';
|
||||
import { coreModule } from 'app/core/core';
|
||||
import { dateTime } from '@grafana/ui';
|
||||
import { UserSession } from 'app/types';
|
||||
|
||||
export class ProfileCtrl {
|
||||
user: any;
|
||||
oldTheme: any;
|
||||
teams: any = [];
|
||||
orgs: any = [];
|
||||
sessions: object[] = [];
|
||||
userForm: any;
|
||||
showTeamsList = false;
|
||||
showOrgsList = false;
|
||||
@@ -15,6 +18,7 @@ export class ProfileCtrl {
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv, private contextSrv, private $location, navModelSrv) {
|
||||
this.getUser();
|
||||
this.getUserSessions();
|
||||
this.getUserTeams();
|
||||
this.getUserOrgs();
|
||||
this.navModel = navModelSrv.getNav('profile', 'profile-settings', 0);
|
||||
@@ -27,6 +31,52 @@ export class ProfileCtrl {
|
||||
});
|
||||
}
|
||||
|
||||
getUserSessions() {
|
||||
this.backendSrv.get('/api/user/auth-tokens').then((sessions: UserSession[]) => {
|
||||
sessions.reverse();
|
||||
|
||||
const found = sessions.findIndex((session: UserSession) => {
|
||||
return session.isActive;
|
||||
});
|
||||
|
||||
if (found) {
|
||||
const now = sessions[found];
|
||||
sessions.splice(found, found);
|
||||
sessions.unshift(now);
|
||||
}
|
||||
|
||||
this.sessions = sessions.map((session: UserSession) => {
|
||||
return {
|
||||
id: session.id,
|
||||
isActive: session.isActive,
|
||||
seenAt: dateTime(session.seenAt).fromNow(),
|
||||
createdAt: dateTime(session.createdAt).format('MMMM DD, YYYY'),
|
||||
clientIp: session.clientIp,
|
||||
browser: session.browser,
|
||||
browserVersion: session.browserVersion,
|
||||
os: session.os,
|
||||
osVersion: session.osVersion,
|
||||
device: session.device,
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
revokeUserSession(tokenId: number) {
|
||||
this.backendSrv
|
||||
.post('/api/user/revoke-auth-token', {
|
||||
authTokenId: tokenId,
|
||||
})
|
||||
.then(() => {
|
||||
this.sessions = this.sessions.filter((session: UserSession) => {
|
||||
if (session.id === tokenId) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getUserTeams() {
|
||||
this.backendSrv.get('/api/user/teams').then(teams => {
|
||||
this.teams = teams;
|
||||
|
||||
@@ -74,3 +74,32 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h3 class="page-heading">Sessions</h3>
|
||||
<div class="gf-form-group">
|
||||
<table class="filter-table form-inline">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Last seen</th>
|
||||
<th>Logged on</th>
|
||||
<th>IP address</th>
|
||||
<th>Browser & OS</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="session in ctrl.sessions">
|
||||
<td ng-if="session.isActive">Now</td>
|
||||
<td ng-if="!session.isActive">{{session.seenAt}}</td>
|
||||
<td>{{session.createdAt}}</td>
|
||||
<td>{{session.clientIp}}</td>
|
||||
<td>{{session.browser}} on {{session.os}} {{session.osVersion}}</td>
|
||||
<td>
|
||||
<button class="btn btn-danger btn-small" ng-click="ctrl.revokeUserSession(session.id)">
|
||||
<i class="fa fa-power-off"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -48,3 +48,16 @@ export interface UserState {
|
||||
orgId: number;
|
||||
timeZone: string;
|
||||
}
|
||||
|
||||
export interface UserSession {
|
||||
id: number;
|
||||
createdAt: string;
|
||||
clientIp: string;
|
||||
isActive: boolean;
|
||||
seenAt: string;
|
||||
browser: string;
|
||||
browserVersion: string;
|
||||
os: string;
|
||||
osVersion: string;
|
||||
device: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user