Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions app/constants/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ export default {
Accounts_ManuallyApproveNewUsers: {
type: 'valueAsBoolean'
},
Accounts_iframe_enabled: {
type: 'valueAsBoolean'
},
Accounts_Iframe_api_url: {
type: 'valueAsString'
},
Accounts_Iframe_api_method: {
type: 'valueAsString'
},
CROWD_Enable: {
type: 'valueAsBoolean'
},
Expand Down
5 changes: 4 additions & 1 deletion app/lib/methods/getSettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ const loginSettings = [
'Accounts_RegistrationForm_LinkReplacementText',
'Accounts_EmailOrUsernamePlaceholder',
'Accounts_PasswordPlaceholder',
'Accounts_PasswordReset'
'Accounts_PasswordReset',
'Accounts_iframe_enabled',
'Accounts_Iframe_api_url',
'Accounts_Iframe_api_method'
];

const serverInfoUpdate = async(serverInfo, iconSetting) => {
Expand Down
70 changes: 65 additions & 5 deletions app/views/AuthenticationWebView.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,37 @@ const userAgent = isIOS
? 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1'
: 'Mozilla/5.0 (Linux; Android 6.0.1; SM-G920V Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36';

// iframe uses a postMessage to send the token to the client
// We'll handle this sending the token to the hash of the window.location
// https://docs.rocket.chat/guides/developer-guides/iframe-integration/authentication#iframe-url
// https://github.com/react-native-community/react-native-webview/issues/24#issuecomment-540130141
const injectedJavaScript = `
window.addEventListener('message', ({ data }) => {
if (typeof data === 'object') {
window.location.hash = JSON.stringify(data);
}
});
function wrap(fn) {
return function wrapper() {
var res = fn.apply(this, arguments);
window.ReactNativeWebView.postMessage(window.location.href);
return res;
}
}
history.pushState = wrap(history.pushState);
history.replaceState = wrap(history.replaceState);
window.addEventListener('popstate', function() {
window.ReactNativeWebView.postMessage(window.location.href);
});
`;

class AuthenticationWebView extends React.PureComponent {
static propTypes = {
navigation: PropTypes.object,
route: PropTypes.object,
server: PropTypes.string,
Accounts_Iframe_api_url: PropTypes.bool,
Accounts_Iframe_api_method: PropTypes.bool,
theme: PropTypes.string
}

Expand All @@ -30,7 +56,8 @@ class AuthenticationWebView extends React.PureComponent {
logging: false,
loading: false
};
this.redirectRegex = new RegExp(`(?=.*(${ props.server }))(?=.*(credentialToken))(?=.*(credentialSecret))`, 'g');
this.oauthRedirectRegex = new RegExp(`(?=.*(${ props.server }))(?=.*(credentialToken))(?=.*(credentialSecret))`, 'g');
this.iframeRedirectRegex = new RegExp(`(?=.*(${ props.server }))(?=.*(event|loginToken|token))`, 'g');
}

componentWillUnmount() {
Expand Down Expand Up @@ -64,6 +91,15 @@ class AuthenticationWebView extends React.PureComponent {
// eslint-disable-next-line react/sort-comp
debouncedLogin = debounce(params => this.login(params), 3000);

tryLogin = debounce(async() => {
const { Accounts_Iframe_api_url, Accounts_Iframe_api_method } = this.props;
const data = await fetch(Accounts_Iframe_api_url, { method: Accounts_Iframe_api_method }).then(response => response.json());
const resume = data?.login || data?.loginToken;
if (resume) {
this.login({ resume });
}
}, 3000, true)

onNavigationStateChange = (webViewState) => {
const url = decodeURIComponent(webViewState.url);
const { route } = this.props;
Expand All @@ -86,25 +122,47 @@ class AuthenticationWebView extends React.PureComponent {
}

if (authType === 'oauth') {
if (this.redirectRegex.test(url)) {
if (this.oauthRedirectRegex.test(url)) {
const parts = url.split('#');
const credentials = JSON.parse(parts[1]);
this.login({ oauth: { ...credentials } });
}
}

if (authType === 'iframe') {
if (this.iframeRedirectRegex.test(url)) {
const parts = url.split('#');
const credentials = JSON.parse(parts[1]);
switch (credentials.event) {
case 'try-iframe-login':
this.tryLogin();
break;
case 'login-with-token':
this.login({ resume: credentials.token || credentials.loginToken });
break;
default:
// Do nothing
}
}
}
}

render() {
const { loading } = this.state;
const { route, theme } = this.props;
const { url } = route.params;
const { url, authType } = route.params;
const isIframe = authType === 'iframe';

return (
<>
<StatusBar theme={theme} />
<WebView
source={{ uri: url }}
userAgent={userAgent}
// https://github.com/react-native-community/react-native-webview/issues/24#issuecomment-540130141
onMessage={({ nativeEvent }) => this.onNavigationStateChange(nativeEvent)}
onNavigationStateChange={this.onNavigationStateChange}
injectedJavaScript={isIframe ? injectedJavaScript : undefined}
onLoadStart={() => {
this.setState({ loading: true });
}}
Expand All @@ -119,14 +177,16 @@ class AuthenticationWebView extends React.PureComponent {
}

const mapStateToProps = state => ({
server: state.server.server
server: state.server.server,
Accounts_Iframe_api_url: state.settings.Accounts_Iframe_api_url,
Accounts_Iframe_api_method: state.settings.Accounts_Iframe_api_method
});

AuthenticationWebView.navigationOptions = ({ route, navigation }) => {
const { authType } = route.params;
return {
headerLeft: () => <CloseModalButton navigation={navigation} />,
title: authType === 'saml' || authType === 'cas' ? 'SSO' : 'OAuth'
title: ['saml', 'cas', 'iframe'].includes(authType) ? 'SSO' : 'OAuth'
};
};

Expand Down
30 changes: 23 additions & 7 deletions app/views/WorkspaceView/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,23 @@ class WorkspaceView extends React.Component {
registrationForm: PropTypes.string,
registrationText: PropTypes.string,
showLoginButton: PropTypes.bool,
Accounts_iframe_enabled: PropTypes.bool,
inviteLinkToken: PropTypes.string
}

get showRegistrationButton() {
const { registrationForm, inviteLinkToken } = this.props;
return registrationForm === 'Public' || (registrationForm === 'Secret URL' && inviteLinkToken?.length);
const { registrationForm, inviteLinkToken, Accounts_iframe_enabled } = this.props;
return !Accounts_iframe_enabled && (registrationForm === 'Public' || (registrationForm === 'Secret URL' && inviteLinkToken?.length));
}

login = () => {
const { navigation, Site_Name } = this.props;
const {
navigation, server, Site_Name, Accounts_iframe_enabled
} = this.props;
if (Accounts_iframe_enabled) {
navigation.navigate('AuthenticationWebView', { url: server, authType: 'iframe' });
return;
}
navigation.navigate('LoginView', { title: Site_Name });
}

Expand All @@ -45,10 +52,20 @@ class WorkspaceView extends React.Component {
navigation.navigate('RegisterView', { title: Site_Name });
}

renderRegisterDisabled = () => {
const { Accounts_iframe_enabled, registrationText, theme } = this.props;
if (Accounts_iframe_enabled) {
return null;
}

return <Text style={[styles.registrationText, { color: themes[theme].auxiliaryText }]}>{registrationText}</Text>;
}

render() {
const {
theme, Site_Name, Site_Url, Assets_favicon_512, server, registrationText, showLoginButton
theme, Site_Name, Site_Url, Assets_favicon_512, server, showLoginButton
} = this.props;

return (
<FormContainer theme={theme} testID='workspace-view'>
<FormContainerInner>
Expand Down Expand Up @@ -77,9 +94,7 @@ class WorkspaceView extends React.Component {
theme={theme}
testID='workspace-view-register'
/>
) : (
<Text style={[styles.registrationText, { color: themes[theme].auxiliaryText }]}>{registrationText}</Text>
)
) : this.renderRegisterDisabled()
}
</FormContainerInner>
</FormContainer>
Expand All @@ -95,6 +110,7 @@ const mapStateToProps = state => ({
Assets_favicon_512: state.settings.Assets_favicon_512,
registrationForm: state.settings.Accounts_RegistrationForm,
registrationText: state.settings.Accounts_RegistrationForm_LinkReplacementText,
Accounts_iframe_enabled: state.settings.Accounts_iframe_enabled,
showLoginButton: getShowLoginButton(state),
inviteLinkToken: state.inviteLinks.token
});
Expand Down