summaryrefslogtreecommitdiffstats
path: root/webapp
diff options
context:
space:
mode:
authorenahum <nahumhbl@gmail.com>2016-08-23 19:06:17 -0300
committerJoram Wilander <jwawilander@gmail.com>2016-08-23 18:06:17 -0400
commit9ab5a7996247c98ed6267b638e1b313e7c4eb8ff (patch)
tree95579883cd48370ee48259b2bec02b124df2f200 /webapp
parente406a92fbbfe36765ab66d9879a9c94546c7c281 (diff)
downloadchat-9ab5a7996247c98ed6267b638e1b313e7c4eb8ff.tar.gz
chat-9ab5a7996247c98ed6267b638e1b313e7c4eb8ff.tar.bz2
chat-9ab5a7996247c98ed6267b638e1b313e7c4eb8ff.zip
PLT-3745 - Deauthorize OAuth Apps (#3852)
* Deauthorize OAuth APIs * Deautorize OAuth Apps Account Settings * Fix typo in client method * Fix issues found by PM * Show help text only when there is at least one authorized app
Diffstat (limited to 'webapp')
-rw-r--r--webapp/client/client.jsx20
-rw-r--r--webapp/components/user_settings/user_settings_security.jsx228
-rw-r--r--webapp/i18n/en.json7
-rw-r--r--webapp/sass/routes/_settings.scss36
4 files changed, 251 insertions, 40 deletions
diff --git a/webapp/client/client.jsx b/webapp/client/client.jsx
index 28d121011..5dda975f6 100644
--- a/webapp/client/client.jsx
+++ b/webapp/client/client.jsx
@@ -1553,6 +1553,26 @@ export default class Client {
end(this.handleResponse.bind(this, 'getOAuthAppInfo', success, error));
}
+ getAuthorizedApps(success, error) {
+ request.
+ get(`${this.getOAuthRoute()}/authorized`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send().
+ end(this.handleResponse.bind(this, 'getAuthorizedApps', success, error));
+ }
+
+ deauthorizeOAuthApp(id, success, error) {
+ request.
+ post(`${this.getOAuthRoute()}/${id}/deauthorize`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send().
+ end(this.handleResponse.bind(this, 'deauthorizeOAuthApp', success, error));
+ }
+
// Routes for Hooks
addIncomingHook(hook, success, error) {
diff --git a/webapp/components/user_settings/user_settings_security.jsx b/webapp/components/user_settings/user_settings_security.jsx
index 769959432..bcffa157c 100644
--- a/webapp/components/user_settings/user_settings_security.jsx
+++ b/webapp/components/user_settings/user_settings_security.jsx
@@ -16,33 +16,12 @@ import Constants from 'utils/constants.jsx';
import $ from 'jquery';
import React from 'react';
-import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage, FormattedTime, FormattedDate} from 'react-intl';
+import {FormattedMessage, FormattedHTMLMessage, FormattedTime, FormattedDate} from 'react-intl';
import {Link} from 'react-router/es6';
-const holders = defineMessages({
- currentPasswordError: {
- id: 'user.settings.security.currentPasswordError',
- defaultMessage: 'Please enter your current password.'
- },
- passwordLengthError: {
- id: 'user.settings.security.passwordLengthError',
- defaultMessage: 'New passwords must be at least {min} characters and at most {max} characters.'
- },
- passwordMatchError: {
- id: 'user.settings.security.passwordMatchError',
- defaultMessage: 'The new passwords you entered do not match.'
- },
- method: {
- id: 'user.settings.security.method',
- defaultMessage: 'Sign-in Method'
- },
- close: {
- id: 'user.settings.security.close',
- defaultMessage: 'Close'
- }
-});
+import icon50 from 'images/icon50x50.png';
-class SecurityTab extends React.Component {
+export default class SecurityTab extends React.Component {
constructor(props) {
super(props);
@@ -56,7 +35,9 @@ class SecurityTab extends React.Component {
this.getDefaultState = this.getDefaultState.bind(this);
this.createPasswordSection = this.createPasswordSection.bind(this);
this.createSignInSection = this.createSignInSection.bind(this);
+ this.createOAuthAppsSection = this.createOAuthAppsSection.bind(this);
this.showQrCode = this.showQrCode.bind(this);
+ this.deauthorizeApp = this.deauthorizeApp.bind(this);
this.state = this.getDefaultState();
}
@@ -74,6 +55,16 @@ class SecurityTab extends React.Component {
};
}
+ componentDidMount() {
+ Client.getAuthorizedApps(
+ (authorizedApps) => {
+ this.setState({authorizedApps, serverError: null}); //eslint-disable-line react/no-did-mount-set-state
+ },
+ (err) => {
+ this.setState({serverError: err.message}); //eslint-disable-line react/no-did-mount-set-state
+ });
+ }
+
submitPassword(e) {
e.preventDefault();
@@ -82,9 +73,8 @@ class SecurityTab extends React.Component {
var newPassword = this.state.newPassword;
var confirmPassword = this.state.confirmPassword;
- const {formatMessage} = this.props.intl;
if (currentPassword === '') {
- this.setState({passwordError: formatMessage(holders.currentPasswordError), serverError: ''});
+ this.setState({passwordError: Utils.localizeMessage('user.settings.security.currentPasswordError', 'Please enter your current password.'), serverError: ''});
return;
}
@@ -98,7 +88,7 @@ class SecurityTab extends React.Component {
}
if (newPassword !== confirmPassword) {
- var defaultState = Object.assign(this.getDefaultState(), {passwordError: formatMessage(holders.passwordMatchError), serverError: ''});
+ var defaultState = Object.assign(this.getDefaultState(), {passwordError: Utils.localizeMessage('user.settings.security.passwordMatchError', 'The new passwords you entered do not match.'), serverError: ''});
this.setState(defaultState);
return;
}
@@ -190,6 +180,23 @@ class SecurityTab extends React.Component {
this.setState({mfaShowQr: true});
}
+ deauthorizeApp(e) {
+ e.preventDefault();
+ const appId = e.currentTarget.getAttribute('data-app');
+ Client.deauthorizeOAuthApp(
+ appId,
+ () => {
+ const authorizedApps = this.state.authorizedApps.filter((app) => {
+ return app.id !== appId;
+ });
+
+ this.setState({authorizedApps, serverError: null});
+ },
+ (err) => {
+ this.setState({serverError: err.message});
+ });
+ }
+
createMfaSection() {
let updateSectionStatus;
let submit;
@@ -686,7 +693,7 @@ class SecurityTab extends React.Component {
return (
<SettingItemMax
- title={this.props.intl.formatMessage(holders.method)}
+ title={Utils.localizeMessage('user.settings.security.method', 'Sign-in Method')}
extraInfo={extraInfo}
inputs={inputs}
server_error={this.state.serverError}
@@ -744,36 +751,180 @@ class SecurityTab extends React.Component {
return (
<SettingItemMin
- title={this.props.intl.formatMessage(holders.method)}
+ title={Utils.localizeMessage('user.settings.security.method', 'Sign-in Method')}
describe={describe}
updateSection={updateSectionStatus}
/>
);
}
+ createOAuthAppsSection() {
+ let updateSectionStatus;
+
+ if (this.props.activeSection === 'apps') {
+ let apps;
+ if (this.state.authorizedApps && this.state.authorizedApps.length > 0) {
+ apps = this.state.authorizedApps.map((app) => {
+ const homepage = (
+ <a
+ href={app.homepage}
+ target='_blank'
+ rel='noopener noreferrer'
+ >
+ {app.homepage}
+ </a>
+ );
+
+ return (
+ <div
+ key={app.id}
+ className='padding-bottom x2 authorized-app'
+ >
+ <div className='col-sm-10'>
+ <div className='authorized-app__name'>
+ {app.name}
+ <span className='authorized-app__url'>
+ {' -'} {homepage}
+ </span>
+ </div>
+ <div className='authorized-app__description'>{app.description}</div>
+ <div className='authorized-app__deauthorize'>
+ <a
+ href='#'
+ data-app={app.id}
+ onClick={this.deauthorizeApp}
+ >
+ <FormattedMessage
+ id='user.settings.security.deauthorize'
+ defaultMessage='Deauthorize'
+ />
+ </a>
+ </div>
+ </div>
+ <div className='col-sm-2 pull-right'>
+ <img
+ alt={app.name}
+ src={app.icon_url || icon50}
+ />
+ </div>
+ <br/>
+ </div>
+ );
+ });
+ } else {
+ apps = (
+ <div className='padding-bottom x2 authorized-app'>
+ <div className='col-sm-12'>
+ <div className='setting-list__hint'>
+ <FormattedMessage
+ id='user.settings.security.noApps'
+ defaultMessage='No OAuth 2.0 Applications are authorized.'
+ />
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+ const inputs = [];
+ let wrapperClass;
+ let helpText;
+ if (Array.isArray(apps)) {
+ wrapperClass = 'authorized-apps__wrapper';
+
+ helpText = (
+ <div className='authorized-apps__help'>
+ <FormattedMessage
+ id='user.settings.security.oauthAppsHelp'
+ defaultMessage='Applications act on your behalf to access your data based on the permissions you grant them.'
+ />
+ </div>
+ );
+ }
+
+ inputs.push(
+ <div
+ className={wrapperClass}
+ key='authorizedApps'
+ >
+ {apps}
+ </div>
+ );
+
+ updateSectionStatus = function updateSection(e) {
+ this.props.updateSection('');
+ this.setState({serverError: null});
+ e.preventDefault();
+ }.bind(this);
+
+ const title = (
+ <div>
+ <FormattedMessage
+ id='user.settings.security.oauthApps'
+ defaultMessage='OAuth 2.0 Applications'
+ />
+ {helpText}
+ </div>
+ );
+
+ return (
+ <SettingItemMax
+ title={title}
+ inputs={inputs}
+ server_error={this.state.serverError}
+ updateSection={updateSectionStatus}
+ width='full'
+ />
+ );
+ }
+
+ updateSectionStatus = function updateSection() {
+ this.props.updateSection('apps');
+ }.bind(this);
+
+ return (
+ <SettingItemMin
+ title={Utils.localizeMessage('user.settings.security.oauthApps', 'OAuth 2.0 Applications')}
+ describe={
+ <FormattedMessage
+ id='user.settings.security.oauthAppsDescription'
+ defaultMessage="Click 'Edit' to manage your OAuth 2.0 Applications"
+ />
+ }
+ updateSection={updateSectionStatus}
+ />
+ );
+ }
+
render() {
const user = this.props.user;
+ const config = window.mm_config;
const passwordSection = this.createPasswordSection();
let numMethods = 0;
- numMethods = global.window.mm_config.EnableSignUpWithGitLab === 'true' ? numMethods + 1 : numMethods;
- numMethods = global.window.mm_config.EnableSignUpWithGoogle === 'true' ? numMethods + 1 : numMethods;
- numMethods = global.window.mm_config.EnableLdap === 'true' ? numMethods + 1 : numMethods;
- numMethods = global.window.mm_config.EnableSaml === 'true' ? numMethods + 1 : numMethods;
+ numMethods = config.EnableSignUpWithGitLab === 'true' ? numMethods + 1 : numMethods;
+ numMethods = config.EnableSignUpWithGoogle === 'true' ? numMethods + 1 : numMethods;
+ numMethods = config.EnableLdap === 'true' ? numMethods + 1 : numMethods;
+ numMethods = config.EnableSaml === 'true' ? numMethods + 1 : numMethods;
let signInSection;
- if (global.window.mm_config.EnableSignUpWithEmail === 'true' && numMethods > 0) {
+ if (config.EnableSignUpWithEmail === 'true' && numMethods > 0) {
signInSection = this.createSignInSection();
}
let mfaSection;
- if (global.window.mm_config.EnableMultifactorAuthentication === 'true' &&
+ if (config.EnableMultifactorAuthentication === 'true' &&
global.window.mm_license.IsLicensed === 'true' &&
(user.auth_service === '' || user.auth_service === Constants.LDAP_SERVICE)) {
mfaSection = this.createMfaSection();
}
+ let oauthSection;
+ if (config.EnableOAuthServiceProvider === 'true') {
+ oauthSection = this.createOAuthAppsSection();
+ }
+
return (
<div>
<div className='modal-header'>
@@ -781,7 +932,7 @@ class SecurityTab extends React.Component {
type='button'
className='close'
data-dismiss='modal'
- aria-label={this.props.intl.formatMessage(holders.close)}
+ aria-label={Utils.localizeMessage('user.settings.security.close', 'Close')}
onClick={this.props.closeModal}
>
<span aria-hidden='true'>{'×'}</span>
@@ -814,6 +965,8 @@ class SecurityTab extends React.Component {
<div className='divider-light'/>
{mfaSection}
<div className='divider-light'/>
+ {oauthSection}
+ <div className='divider-light'/>
{signInSection}
<div className='divider-dark'/>
<br></br>
@@ -849,7 +1002,6 @@ SecurityTab.defaultProps = {
activeSection: ''
};
SecurityTab.propTypes = {
- intl: intlShape.isRequired,
user: React.PropTypes.object,
activeSection: React.PropTypes.string,
updateSection: React.PropTypes.func,
@@ -858,5 +1010,3 @@ SecurityTab.propTypes = {
collapseModal: React.PropTypes.func.isRequired,
setEnforceFocus: React.PropTypes.func.isRequired
};
-
-export default injectIntl(SecurityTab);
diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json
index 378812455..0fb75e8b8 100644
--- a/webapp/i18n/en.json
+++ b/webapp/i18n/en.json
@@ -1961,5 +1961,10 @@
"web.footer.terms": "Terms",
"web.header.back": "Back",
"web.root.signup_info": "All team communication in one place, searchable and accessible anywhere",
- "youtube_video.notFound": "Video not found"
+ "youtube_video.notFound": "Video not found",
+ "user.settings.security.deauthorize": "Deauthorize",
+ "user.settings.security.noApps": "No OAuth 2.0 Applications are authorized.",
+ "user.settings.security.oauthApps": "OAuth 2.0 Applications",
+ "user.settings.security.oauthAppsDescription": "Click 'Edit' to manage your OAuth 2.0 Applications",
+ "user.settings.security.oauthAppsHelp": "Applications act on your behalf to access your data based on the permissions you grant them."
}
diff --git a/webapp/sass/routes/_settings.scss b/webapp/sass/routes/_settings.scss
index 36a1acf76..6fa8c26a7 100644
--- a/webapp/sass/routes/_settings.scss
+++ b/webapp/sass/routes/_settings.scss
@@ -7,6 +7,42 @@
max-height: 300px;
max-width: 560px;
}
+
+ .authorized-apps__help {
+ font-size: 13px;
+ font-weight: 400;
+ margin-top: 7px;
+ }
+
+ .authorized-apps__wrapper {
+ background-color: #fff;
+ padding: 10px 0;
+ }
+
+ .authorized-app {
+ display: inline-block;
+ width: 100%;
+
+ &:not(:last-child) {
+ border-bottom: 1px solid #ccc;
+ margin-bottom: 10px;
+ }
+
+ .authorized-app__name {
+ font-weight: 600;
+ }
+
+ .authorized-app__url {
+ font-size: 13px;
+ font-weight: 400;
+ }
+
+ .authorized-app__description,
+ .authorized-app__deauthorize {
+ font-size: 13px;
+ margin: 5px 0;
+ }
+ }
}
.modal {