diff --git a/app/models/user.rb b/app/models/user.rb
index a8ca2a1aed2..352cdd32663 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -463,7 +463,7 @@ def can_change_admin_flag?
def editing_self?(options = {})
options[:controller].to_s == 'users' &&
- options[:action] =~ /edit|update/ &&
+ options[:action] =~ /edit|update|invalidate_jwt/ &&
options[:id].to_i == id ||
options[:controller].to_s =~ /\Aapi\/v\d+\/users\Z/ &&
options[:action] =~ /show|update/ &&
diff --git a/app/views/jwt_tokens/_jwt_tokens_tab.html.erb b/app/views/jwt_tokens/_jwt_tokens_tab.html.erb
new file mode 100644
index 00000000000..212031b2fa4
--- /dev/null
+++ b/app/views/jwt_tokens/_jwt_tokens_tab.html.erb
@@ -0,0 +1,5 @@
+
<% caption = @user.inherited_admin? ? _('Admin rights are currently inherited from a user group') : '' %>
<%= checkbox_f f, :admin, help_block: caption if User.current.can_change_admin_flag? %>
diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb
index 6b1a4bdae92..735f34bddd5 100644
--- a/test/controllers/users_controller_test.rb
+++ b/test/controllers/users_controller_test.rb
@@ -167,6 +167,15 @@ class UsersControllerTest < ActionController::TestCase
assert_nil user.jwt_secret
end
+ test "User should be able to invalidate jwt for self" do
+ User.current = users(:one)
+ user = User.current
+ FactoryBot.build(:jwt_secret, token: 'test_jwt_secret', user: user)
+ patch :invalidate_jwt, params: { :id => user.id }
+ user.reload
+ assert_nil user.jwt_secret
+ end
+
test 'user with edit users permission should be able to invalidate jwt for another user' do
User.current = setup_user "edit", "users"
user = users(:two)
diff --git a/webpack/assets/javascripts/react_app/components/componentRegistry.js b/webpack/assets/javascripts/react_app/components/componentRegistry.js
index 5e8c9a509a7..2e5b8e66c4d 100644
--- a/webpack/assets/javascripts/react_app/components/componentRegistry.js
+++ b/webpack/assets/javascripts/react_app/components/componentRegistry.js
@@ -44,6 +44,7 @@ import LabelIcon from './common/LabelIcon';
import { WelcomeAuthSource } from './AuthSource/Welcome';
import { WelcomeConfigReports } from './ConfigReports/Welcome';
import { WelcomeArchitecture } from './Architectures/Welcome';
+import JwtTokens from './users/JwtTokens/JwtTokens';
const componentRegistry = {
registry: forceSingleton('component_registry', () => ({})),
@@ -142,6 +143,7 @@ const coreComponents = [
{ name: 'SettingsTable', type: SettingsTable },
{ name: 'SettingUpdateModal', type: SettingUpdateModal },
{ name: 'PersonalAccessTokens', type: PersonalAccessTokens },
+ { name: 'JwtTokens', type: JwtTokens },
{ name: 'ClipboardCopy', type: ClipboardCopy },
{ name: 'LabelIcon', type: LabelIcon },
{
diff --git a/webpack/assets/javascripts/react_app/components/users/JwtTokens/JwtTokens.js b/webpack/assets/javascripts/react_app/components/users/JwtTokens/JwtTokens.js
new file mode 100644
index 00000000000..a3d21b1b407
--- /dev/null
+++ b/webpack/assets/javascripts/react_app/components/users/JwtTokens/JwtTokens.js
@@ -0,0 +1,61 @@
+import React, { Fragment } from 'react';
+import { Button } from '@patternfly/react-core';
+import { KeyIcon } from '@patternfly/react-icons';
+import { useDispatch } from 'react-redux';
+import PropTypes from 'prop-types';
+import { useAPI } from '../../../common/hooks/API/APIHooks';
+import { openConfirmModal } from '../../ConfirmModal';
+import { APIActions } from '../../../redux/API';
+import { translate as __ } from '../../../common/I18n';
+
+const JwtTokens = ({ userId }) => {
+ const dispatch = useDispatch();
+ return (
+
+
+
+
+
+
+
+
+ {__('JWT Tokens')}
+
+ {__(
+ 'By invalidating your JSON Web Tokens (JWTs), you will no longer be able to register hosts by using your existing JWTs.'
+ )}
+
+
+ |
+
+
+
+
+ );
+};
+
+JwtTokens.propTypes = {
+ userId: PropTypes.string.isRequired,
+};
+
+export default JwtTokens;