signup_controller.jsx 17 KB
Newer Older
1
2
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
3

4
import PropTypes from 'prop-types';
5
import React from 'react';
6
import {FormattedMessage} from 'react-intl';
7
import {Link} from 'react-router-dom';
8

9
10
import {isEmpty} from 'lodash';

11
12
import {Client4} from 'mattermost-redux/client';

13
import {browserHistory} from 'utils/browser_history';
14
import * as GlobalActions from 'actions/global_actions';
15
import logoImage from 'images/logo.png';
16
import AnnouncementBar from 'components/announcement_bar';
17
import BackButton from 'components/common/back_button';
18
import FormError from 'components/form_error';
19
import LocalizedIcon from 'components/localized_icon';
20

21
import LoadingScreen from 'components/loading_screen';
22
import {Constants} from 'utils/constants';
23
import {t} from 'utils/i18n';
24

25
export default class SignupController extends React.PureComponent {
26
27
28
29
30
31
32
33
    static propTypes = {
        location: PropTypes.object,
        loggedIn: PropTypes.bool.isRequired,
        isLicensed: PropTypes.bool.isRequired,
        enableOpenServer: PropTypes.bool.isRequired,
        noAccounts: PropTypes.bool.isRequired,
        enableSignUpWithEmail: PropTypes.bool.isRequired,
        enableSignUpWithGitLab: PropTypes.bool.isRequired,
34
        enableSignUpWithPhabricator: PropTypes.bool.isRequired,
35
36
        enableSignUpWithGoogle: PropTypes.bool.isRequired,
        enableSignUpWithOffice365: PropTypes.bool.isRequired,
37
        enableSignUpWithOpenId: PropTypes.bool.isRequired,
38
39
40
41
42
        enableLDAP: PropTypes.bool.isRequired,
        enableSAML: PropTypes.bool.isRequired,
        samlLoginButtonText: PropTypes.string,
        siteName: PropTypes.string,
        usedBefore: PropTypes.string,
43
        ldapLoginFieldName: PropTypes.string.isRequired,
44
45
        openidButtonText: PropTypes.string,
        openidButtonColor: PropTypes.string,
46
47
        subscriptionStats: PropTypes.object,
        isCloud: PropTypes.bool,
48
49
        actions: PropTypes.shape({
            removeGlobalItem: PropTypes.func.isRequired,
50
51
            getTeamInviteInfo: PropTypes.func.isRequired,
            addUserToTeamFromInvite: PropTypes.func.isRequired,
52
53
54
        }).isRequired,
    }

55
56
57
58
59
60
61
62
    constructor(props) {
        super(props);

        let loading = false;
        let serverError = '';
        let noOpenServerError = false;
        let usedBefore = false;

63
64
        if (this.props.location.search) {
            const params = new URLSearchParams(this.props.location.search);
65
66
67
            let token = params.get('t');
            if (token == null) {
                token = '';
68
69
70
71
72
            }
            let inviteId = params.get('id');
            if (inviteId == null) {
                inviteId = '';
            }
73

Joram Wilander's avatar
Joram Wilander committed
74
75
            if (inviteId) {
                loading = true;
76
77
            } else if (!this.props.loggedIn) {
                usedBefore = props.usedBefore;
78
            } else if (!inviteId && !this.props.enableOpenServer && !this.props.noAccounts) {
79
80
81
82
83
84
85
86
87
88
89
90
91
92
                noOpenServerError = true;
                serverError = (
                    <FormattedMessage
                        id='signup_user_completed.no_open_server'
                        defaultMessage='This server does not allow open signups.  Please speak with your Administrator to receive an invitation.'
                    />
                );
            }
        }

        this.state = {
            loading,
            serverError,
            noOpenServerError,
93
            usedBefore,
94
95
96
97
        };
    }

    componentDidMount() {
98
        this.props.actions.removeGlobalItem('team');
99
100
101
102
103
104
105
106
107
        let isFreeTierWithNoFreeSeats = false;
        if (!isEmpty(this.props.subscriptionStats)) {
            const {is_paid_tier: isPaidTier, remaining_seats: remainingSeats} = this.props.subscriptionStats;
            isFreeTierWithNoFreeSeats = isPaidTier === 'false' && remainingSeats <= 0;
        }

        if (this.props.isCloud && isFreeTierWithNoFreeSeats) {
            browserHistory.push('/error?type=max_free_users_reached');
        } else if (this.props.location.search) {
108
            const params = new URLSearchParams(this.props.location.search);
109
            const token = params.get('t') || '';
110
            const inviteId = params.get('id') || '';
111

112
            const userLoggedIn = this.props.loggedIn;
Joram Wilander's avatar
Joram Wilander committed
113

114
            if ((inviteId || token) && userLoggedIn) {
115
116
                this.addUserToTeamFromInvite(token, inviteId);
            } else if (inviteId) {
117
                this.getInviteInfo(inviteId);
118
            } else if (userLoggedIn) {
enahum's avatar
enahum committed
119
                GlobalActions.redirectUserToDefaultTeam();
120
121
122
123
            }
        }
    }

124
125
126
127
128
129
130
131
132
    addUserToTeamFromInvite = async (token, inviteId) => {
        const {data: team, error} = await this.props.actions.addUserToTeamFromInvite(token, inviteId);
        if (team) {
            browserHistory.push('/' + team.name + `/channels/${Constants.DEFAULT_CHANNEL}`);
        } else if (error) {
            this.handleInvalidInvite(error);
        }
    }

133
    getInviteInfo = async (inviteId) => {
134
        const {data, error} = await this.props.actions.getTeamInviteInfo(inviteId);
135
136
137
138
139
        if (data) {
            this.setState({
                serverError: '',
                loading: false,
            });
140
141
        } else if (error) {
            this.handleInvalidInvite(error);
142
143
144
        }
    }

145
146
    handleInvalidInvite = (err) => {
        let serverError;
147
        if (err.server_error_id === 'store.sql_user.save.max_accounts.app_error') {
148
            serverError = err.message;
149
150
        } else if (err.server_error_id === 'api.team.add_user_to_team_from_invite.guest.app_error') {
            serverError = err.message;
151
152
153
154
155
156
157
158
159
160
161
162
        } else {
            serverError = (
                <FormattedMessage
                    id='signup_user_completed.invalid_invite'
                    defaultMessage='The invite link was invalid.  Please speak with your Administrator to receive an invitation.'
                />
            );
        }

        this.setState({
            noOpenServerError: true,
            loading: false,
163
            serverError,
164
165
166
        });
    }

167
    renderSignupControls = () => {
168
169
        let signupControls = [];

170
        if (this.props.enableSignUpWithEmail) {
171
172
173
174
175
176
177
            signupControls.push(
                <Link
                    className='btn btn-custom-login btn--full email'
                    key='email'
                    to={'/signup_email' + window.location.search}
                >
                    <span>
178
                        <LocalizedIcon
179
                            className='icon fa fa-envelope'
180
181
                            component='span'
                            title={{id: t('signup.email.icon'), defaultMessage: 'Email Icon'}}
182
                        />
183
184
185
186
187
                        <FormattedMessage
                            id='signup.email'
                            defaultMessage='Email and Password'
                        />
                    </span>
188
                </Link>,
189
190
191
            );
        }

192
        if (this.props.enableSignUpWithGitLab) {
193
194
195
196
            signupControls.push(
                <a
                    className='btn btn-custom-login btn--full gitlab'
                    key='gitlab'
197
                    href={Client4.getOAuthRoute() + '/gitlab/signup' + window.location.search}
198
199
                >
                    <span>
Asaad Mahmood's avatar
Asaad Mahmood committed
200
201
202
203
                        <span className='icon'/>
                        <span>
                            <FormattedMessage
                                id='signup.gitlab'
204
                                defaultMessage='GitLab Single Sign-On'
Asaad Mahmood's avatar
Asaad Mahmood committed
205
206
                            />
                        </span>
207
                    </span>
208
                </a>,
209
210
211
            );
        }

212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
        if (this.props.enableSignUpWithPhabricator) {
            signupControls.push(
                <a
                    className='btn btn-custom-login btn--full phabricator'
                    key='phabricator'
                    href={Client4.getOAuthRoute() + '/phabricator/signup' + window.location.search}
                >
                    <span>
                        <span className='icon'/>
                        <span>
                            <FormattedMessage
                                id='signup.phabricator'
                                defaultMessage='Phabricator Single Sign-On'
                            />
                        </span>
                    </span>
James Addison's avatar
James Addison committed
228
                </a>,
229
230
231
            );
        }

232
        if (this.props.isLicensed && this.props.enableSignUpWithGoogle) {
233
234
235
236
            signupControls.push(
                <a
                    className='btn btn-custom-login btn--full google'
                    key='google'
237
                    href={Client4.getOAuthRoute() + '/google/signup' + window.location.search}
238
239
                >
                    <span>
Asaad Mahmood's avatar
Asaad Mahmood committed
240
241
242
243
244
245
246
                        <span className='icon'/>
                        <span>
                            <FormattedMessage
                                id='signup.google'
                                defaultMessage='Google Account'
                            />
                        </span>
247
                    </span>
248
                </a>,
249
250
251
            );
        }

252
        if (this.props.isLicensed && this.props.enableSignUpWithOffice365) {
253
254
255
256
            signupControls.push(
                <a
                    className='btn btn-custom-login btn--full office365'
                    key='office365'
257
                    href={Client4.getOAuthRoute() + '/office365/signup' + window.location.search}
258
259
                >
                    <span>
Asaad Mahmood's avatar
Asaad Mahmood committed
260
261
262
263
264
265
266
                        <span className='icon'/>
                        <span>
                            <FormattedMessage
                                id='signup.office365'
                                defaultMessage='Office 365'
                            />
                        </span>
267
                    </span>
268
                </a>,
269
            );
270
271
        }

272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
        if (this.props.isLicensed && this.props.enableSignUpWithOpenId) {
            const buttonStyle = {};
            if (this.props.openidButtonColor) {
                buttonStyle.backgroundColor = this.props.openidButtonColor;
            }
            let buttonText = (
                <FormattedMessage
                    id='login.openid'
                    defaultMessage='Open Id'
                />
            );
            if (this.props.openidButtonText) {
                buttonText = this.props.openidButtonText;
            }
            signupControls.push(
                <a
                    id='OpenIdButton'
                    className='btn btn-custom-login btn--full openid'
                    style={buttonStyle}
                    key='openid'
                    href={Client4.getOAuthRoute() + '/openid/signup' + window.location.search}
                >
                    <span>
                        <span>
                            {buttonText}
                        </span>
                    </span>
                </a>,
            );
        }

303
        if (this.props.isLicensed && this.props.enableLDAP) {
304
305
306
307
            const params = new URLSearchParams(this.props.location.search);
            params.append('extra', 'create_ldap');
            const query = '?' + params.toString();

308
309
310
311
312
313
314
315
316
            let LDAPText = (
                <FormattedMessage
                    id='signup.ldap'
                    defaultMessage='AD/LDAP Credentials'
                />
            );
            if (this.props.ldapLoginFieldName) {
                LDAPText = this.props.ldapLoginFieldName;
            }
317
318
319
320
            signupControls.push(
                <Link
                    className='btn btn-custom-login btn--full ldap'
                    key='ldap'
321
                    to={'/login' + query}
322
323
                >
                    <span>
324
                        <LocalizedIcon
325
                            className='icon fa fa-folder-open fa--margin-top'
326
327
                            component='span'
                            title={{id: t('signup.ldap.icon'), defaultMessage: 'AD/LDAP Icon'}}
328
                        />
Asaad Mahmood's avatar
Asaad Mahmood committed
329
                        <span>
330
                            {LDAPText}
Asaad Mahmood's avatar
Asaad Mahmood committed
331
                        </span>
332
                    </span>
333
                </Link>,
334
335
336
            );
        }

337
        if (this.props.isLicensed && this.props.enableSAML) {
338
339
340
341
342
343
344
345
            let query = '';
            if (window.location.search) {
                query = '&action=signup';
            } else {
                query = '?action=signup';
            }

            signupControls.push(
346
                <Link
347
348
                    className='btn btn-custom-login btn--full saml'
                    key='saml'
349
                    to={'/login/sso/saml' + window.location.search + query}
350
351
                >
                    <span>
352
                        <LocalizedIcon
353
                            className='icon fa fa-lock fa--margin-top'
354
355
                            component='span'
                            title={{id: t('signup.saml.icon'), defaultMessage: 'SAML Icon'}}
356
                        />
Asaad Mahmood's avatar
Asaad Mahmood committed
357
                        <span>
358
                            {this.props.samlLoginButtonText}
Asaad Mahmood's avatar
Asaad Mahmood committed
359
                        </span>
360
                    </span>
361
                </Link>,
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
            );
        }

        if (signupControls.length === 0) {
            const signupDisabledError = (
                <FormattedMessage
                    id='signup_user_completed.none'
                    defaultMessage='No user creation method has been enabled. Please contact an administrator for access.'
                />
            );
            signupControls = (
                <FormError
                    error={signupDisabledError}
                    margin={true}
                />
            );
378
        } else if (signupControls.length === 1) {
379
            if (this.props.enableSignUpWithEmail) {
380
                return browserHistory.push('/signup_email' + window.location.search);
381
            } else if (this.props.isLicensed && this.props.enableLDAP) {
382
                return browserHistory.push('/login' + window.location.search);
383
            }
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
        }

        return signupControls;
    }

    render() {
        if (this.state.loading) {
            return (<LoadingScreen/>);
        }

        if (this.state.usedBefore) {
            return (
                <div>
                    <FormattedMessage
                        id='signup_user_completed.expired'
                        defaultMessage="You've already completed the signup process for this invitation or this invitation has expired."
                    />
                </div>
            );
        }

        let serverError = null;
        if (this.state.serverError) {
            serverError = (
                <div className={'form-group has-error'}>
                    <label className='control-label'>{this.state.serverError}</label>
                </div>
            );
        }

414
        let signupControls;
415
416
        if (this.state.noOpenServerError || this.state.usedBefore) {
            signupControls = null;
417
418
        } else {
            signupControls = this.renderSignupControls();
419
420
421
422
        }

        return (
            <div>
423
                <AnnouncementBar/>
424
                <BackButton/>
425
426
427
                <div className='col-sm-12'>
                    <div className='signup-team__container'>
                        <img
428
                            alt={'signup team logo'}
429
430
431
432
                            className='signup-team-logo'
                            src={logoImage}
                        />
                        <div className='signup__content'>
433
                            <h1>{this.props.siteName}</h1>
434
435
436
437
438
                            <h4 className='color--light'>
                                <FormattedMessage
                                    id='web.root.signup_info'
                                />
                            </h4>
439
                            <div className='mt-8'>
440
441
442
443
444
445
446
447
448
449
                                <h5><strong>
                                    <FormattedMessage
                                        id='signup.title'
                                        defaultMessage='Create an account with:'
                                    />
                                </strong></h5>
                            </div>
                            {signupControls}
                            {serverError}
                        </div>
450
451
452
453
454
455
456
                        <span className='color--light'>
                            <FormattedMessage
                                id='signup_user_completed.haveAccount'
                                defaultMessage='Already have an account?'
                            />
                            {' '}
                            <Link
Joram Wilander's avatar
Joram Wilander committed
457
                                to={'/login' + this.props.location.search}
458
459
460
461
462
463
464
                            >
                                <FormattedMessage
                                    id='signup_user_completed.signIn'
                                    defaultMessage='Click here to sign in.'
                                />
                            </Link>
                        </span>
465
466
467
468
469
                    </div>
                </div>
            </div>
        );
    }
470
}