Skip to content
This repository has been archived by the owner on Jun 6, 2024. It is now read-only.

Commit

Permalink
[Web Portal] revoke browser tokens when user changes password / logout (
Browse files Browse the repository at this point in the history
  • Loading branch information
sunqinzheng authored Nov 13, 2019
1 parent 19dc616 commit 094b989
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 7 deletions.
13 changes: 13 additions & 0 deletions src/rest-server/src/controllers/v2/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

// module dependencies
const jwt = require('jsonwebtoken');
const userModel = require('@pai/models/v2/user');
const createError = require('@pai/utils/error');
const authConfig = require('@pai/config/authn');
const logger = require('@pai/config/logger');
const groupModel = require('@pai/models/v2/group');
const vcModel = require('@pai/models/v2/virtual-cluster');
const tokenModel = require('@pai/models/token');

const getUserVCs = async (username) => {
const userInfo = await userModel.getUser(username);
Expand Down Expand Up @@ -344,6 +347,16 @@ const updateUserPassword = async (req, res, next) => {
if (req.user.admin || newUserValue['password'] === userValue['password']) {
newUserValue['password'] = newPassword;
await userModel.updateUser(username, newUserValue, true);
// try to revoke browser tokens
try {
await tokenModel.batchRevoke(username, (token) => {
const data = jwt.decode(token);
return !data.application;
});
} catch (err) {
logger.error('Failed to revoke tokens after password is updated', err);
// pass
}
return res.status(201).json({
message: 'update user password successfully.',
});
Expand Down
12 changes: 12 additions & 0 deletions src/rest-server/src/models/token.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,17 @@ const revoke = async (token) => {
await k8sSecret.replace(namespace, username, result);
};

const batchRevoke = async (username, filter) => {
const item = await k8sSecret.get(namespace, username);
const result = purge(item || {});
for (const [key, val] of Object.entries(result)) {
if (filter(val)) {
delete result[key];
}
}
await k8sSecret.replace(namespace, username, result);
};

const verify = async (token) => {
const payload = jwt.verify(token, secret);
const username = payload.username;
Expand All @@ -141,6 +152,7 @@ const verify = async (token) => {
module.exports = {
list,
create,
batchRevoke,
revoke,
verify,
};
7 changes: 6 additions & 1 deletion src/webportal/src/app/user/fabric/conn.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import cookies from 'js-cookie';
import config from '../../config/webportal.config';
import { checkToken } from '../user-auth/user-auth.component';
import { clearToken } from '../user-logout/user-logout.component';
Expand Down Expand Up @@ -90,13 +91,17 @@ export const updateUserPasswordRequest = async (
) => {
const url = `${config.restServerUri}/api/v2/user/${username}/password`;
const token = checkToken();
return fetchWrapper(url, {
const result = await fetchWrapper(url, {
method: 'PUT',
headers: {
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ newPassword, oldPassword }),
});
if (username === cookies.get('user')) {
clearToken();
}
return result;
};

export const updateUserEmailRequest = async (username, email) => {
Expand Down
4 changes: 4 additions & 0 deletions src/webportal/src/app/user/fabric/user-profile/header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,10 @@ const UserProfileHeader = ({ userInfo, onEditProfile, onEditPassword }) => {
>
<div>
<div>
If password is changed, all browser tokens will be revoked and
you will be logged out.
</div>
<div className={t.mt3}>
<TextField
label='Old Password'
componentRef={oldPasswordRef}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export default function BatchPasswordEditor({ isOpen = false, hide }) {
};

const tdPaddingStyle = c(t.pa3);
const tdLabelStyle = c(tdPaddingStyle, t.tr);
const tdLabelStyle = c(tdPaddingStyle, t.tr, t.vTop);

const { spacing } = getTheme();

Expand All @@ -114,6 +114,7 @@ export default function BatchPasswordEditor({ isOpen = false, hide }) {
<CustomPassword
componentRef={passwordRef}
placeholder='Enter password'
description="User's browser tokens will be revoked if password is changed"
/>
</td>
</tr>
Expand Down
15 changes: 10 additions & 5 deletions src/webportal/src/app/user/fabric/userView/UserEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ export default function UserEditor({
};

const tdPaddingStyle = c(t.pa3);
const tdLabelStyle = c(tdPaddingStyle, t.tr);
const tdLabelStyle = c(tdPaddingStyle, t.tr, t.vTop);

/**
* @type {import('office-ui-fabric-react').IDropdownOption[]}
Expand All @@ -242,7 +242,7 @@ export default function UserEditor({
<Modal
isOpen={isOpen}
isBlocking={true}
containerClassName={mergeStyles({ width: '450px', minWidth: '450px' })}
containerClassName={mergeStyles({ width: '480px', minWidth: '480px' })}
>
<div className={c(t.pa4)}>
<form onSubmit={handleSubmit}>
Expand All @@ -253,8 +253,10 @@ export default function UserEditor({
<table className={c(t.mlAuto, t.mrAuto)}>
<tbody>
<tr>
<td className={tdLabelStyle}>Name</td>
<td className={tdPaddingStyle} style={{ minWidth: '280px' }}>
<td className={tdLabelStyle} style={{ minWidth: '140px' }}>
Name
</td>
<td className={tdPaddingStyle} style={{ minWidth: '248px' }}>
<TextField
id={`NameInput${Math.random()}`}
componentRef={usernameRef}
Expand All @@ -270,6 +272,10 @@ export default function UserEditor({
<CustomPassword
componentRef={passwordRef}
placeholder={isCreate ? 'Enter password' : '******'}
description={
!isCreate &&
"User's browser tokens will be revoked if password is changed"
}
/>
</td>
</tr>
Expand All @@ -294,7 +300,6 @@ export default function UserEditor({
disabled={isAdmin}
onChange={handleVCsChanged}
placeholder='Select an option'
style={{ maxWidth: '248px' }}
/>
</td>
</tr>
Expand Down
11 changes: 11 additions & 0 deletions src/webportal/src/app/user/user-logout/user-logout.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,21 @@ const querystring = require('querystring');
const webportalConfig = require('../../config/webportal.config.js');

const userLogout = (origin = window.location.href) => {
// revoke token
const token = cookies.get('token');
const url = `${webportalConfig.restServerUri}/api/v1/token/${token}`;
fetch(url, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${token}`,
},
}).catch(console.err);
// clear cookies
cookies.remove('user');
cookies.remove('token');
cookies.remove('admin');
cookies.remove('my-jobs');
// redirect
if (webportalConfig.authnMethod === 'basic') {
if (!origin) {
window.location.replace('/index.html');
Expand Down

0 comments on commit 094b989

Please sign in to comment.