import React from 'react' import { FormGroup, ControlLabel, FormControl, HelpBlock, Button, Checkbox, Glyphicon, Overlay, Tooltip, Alert } from 'react-bootstrap' import Spinner from './spinner' import Validate from 'lib/validate' import App from 'app' import Account from 'models/account' import Provider from 'models/provider' class Login extends React.Component { static get defaultProps() {return{ rememberAllowed: false, // if set, show remember password checkbox autoAllowed: false, // if set, allow auto setup of provider domain: null, // if set, only allow this domain address: null, // if set, only allow this username@domain onLogin: null, // callback mode: "login" // one of "login" or "signup" }} constructor(props) { super(props) // validation states can be null, 'success', 'warning', or 'error' this.state = { loading: false, authError: false, // authentication error message username: this.props.address, usernameState: null, // username validation state usernameError: false, // username help message password: null, // the main password field passwordState: null, // password validation state passwordError: false, // password help message password2: null, // password confirmation password2State: null, // password confirm validation state password2Error: false, // password confirm help message requireInvite: false, invite: null, // invite code value inviteState: null, // invite code validation state inviteError: false, // invite code error disabled: false, remember: false // remember is checked? } // prebind: this.onUsernameChange = this.onUsernameChange.bind(this) this.onUsernameBlur = this.onUsernameBlur.bind(this) this.onPassword = this.onPassword.bind(this) this.onPassword2 = this.onPassword2.bind(this) this.onInvite = this.onInvite.bind(this) this.onSubmit = this.onSubmit.bind(this) this.onKeyPress = this.onKeyPress.bind(this) this.onRemember = this.onRemember.bind(this) } componentDidMount() { Validate.loadPasswdLib() if (this.props.domain && this.props.mode == 'signup') { Provider.get(this.props.domain).then(provider => { if (provider.enrollment_policy == 'invite') { this.setState({requireInvite: true}) } }) } } componentDidUpdate(prevProps, prevState) { // If the user changes anything in the domain (which follows the @ sign) // it gets replaced reverted to the domain name. This moves the cursor to // before the @ sign when that happens if (this.props.domain && this.state.username){ let textarea = this.usernameref let start = this.state.username.indexOf('@') if (textarea.selectionStart > start) { textarea.setSelectionRange(start, start) } } } render () { let rememberCheck = "" let submitButton = "" let usernameHelp = null let passwordHelp = null let password2Help = null let password2Elem = null let inviteElem = null let message = null let buttonText = "Log In" /* * disabled for now * if (this.props.rememberAllowed) { let props = { style: {marginTop: "0px"}, onChange: this.onRemember } if (this.state.remember) { rememberCheck = Remember username and password } else { rememberCheck = Remember username and password } } */ if (this.state.authError) { // style may be: success, warning, danger, info message = ( {this.state.authError} ) } if (this.state.usernameError) { usernameHelp = {this.state.usernameError} // let props = {shouldUpdatePosition: true, show:true, placement:"right", // target:this.refs.username} // usernameHelp = ( // // {this.state.usernameError} // // ) } else { //usernameHelp =   } if (this.state.passwordError) { passwordHelp = {this.state.passwordError} // let props = {shouldUpdatePosition: true, show:true, placement:"right", // target:this.refs.password, component: {this}} // passwordHelp = ( // // {this.state.passwordError} // // ) } else { //passwordHelp =   } if (this.props.mode == 'signup') { buttonText = 'Sign Up' if (this.state.password2Error) { password2Help = {this.state.password2Error} } password2Elem = ( Repeat Password this.password2ref = ref} value={this.state.password2 || ""} onChange={this.onPassword2} /> {this.state.password2State == 'success' ? null : } {password2Help} ) if (this.state.requireInvite) { let inviteHelp = null inviteElem = ( Invite Code this.inviteref = ref} /> {inviteHelp} ) } } let buttonProps = { type: "button", onClick: this.onSubmit, disabled: !this.maySubmit() } if (this.state.loading) { submitButton = } else { submitButton = } let usernameDisabled = false let usernameValue = this.state.username || "" if (this.props.address) { usernameDisabled = true usernameValue = this.props.address } const form = (
{message} Username this.usernameref = ref} autoFocus value={usernameValue} disabled={usernameDisabled} onChange={this.onUsernameChange} onBlur={this.onUsernameBlur} /> {this.state.usernameState == 'success' ? null : } {usernameHelp} Password this.passwordref = ref} value={this.state.password || ""} onChange={this.onPassword} /> {this.state.passwordState == 'success' ? null : } {passwordHelp} {password2Elem} {inviteElem} {submitButton} {rememberCheck}
) return form } // // Here we do a partial validation, because the user has not stopped typing. // onUsernameChange(e) { let username = e.target.value.toLowerCase().replace("\n", "") if (this.props.domain) { let [userpart, domainpart] = username.split( new RegExp('@|' + this.props.domain.replace(".", "\\.") + '$') ) username = [userpart, this.props.domain].join('@') } let error = Validate.usernameInteractive(username, this.props.domain) let state = null if (error) { state = 'error' } else { if (username && username.length > 0) { let finalError = Validate.username(username) state = finalError ? null : 'success' } } this.setState({ username: username, usernameState: state, usernameError: error ? error : null }) } // // Here we do a more complete validation, since the user have left the field. // onUsernameBlur(e) { let username = e.target.value.toLowerCase() this.setState({ username: username }) if (username.length > 0) { this.validateUsername(username) } else { this.setState({ usernameState: null, usernameError: null }) } } onPassword(e) { let password = e.target.value this.setState({password: password}) if (password.length > 0) { this.validatePassword(password) } else { this.setState({ passwordState: null, passwordError: null }) } } onPassword2(e) { let password2 = e.target.value this.setState({password2: password2}) this.validatePassword2(password2, this.state.password) } onInvite(e) { this.setState({invite: e.target.value}) } onRemember(e) { let currentValue = e.target.value == 'on' ? true : false let value = !currentValue this.setState({remember: value}) } validateUsername(username) { let error = Validate.username(username, this.props.domain) this.setState({ usernameState: error ? 'error' : 'success', usernameError: error ? error : null }) } validatePassword(password) { let state = null let message = null let result = Validate.passwordStrength(password) if (result) { message = "Time to crack: " + result.crack_times_display.offline_slow_hashing_1e4_per_second if (result.score == 0) { state = 'error' } else if (result.score == 1 || result.score == 2) { state = 'warning' } else { state = 'success' } } this.setState({ passwordState: state, passwordError: message }) this.validatePassword2(this.state.password2, password) } validatePassword2(password2, password) { if (password2) { if (password != password2) { this.setState({ password2State: 'error', password2Error: "Does not match" }) } else { this.setState({ password2State: 'success', password2Error: null }) } } else { this.setState({ password2State: null, password2Error: null }) } } maySubmit() { let ok = ( !this.state.loading && !this.state.usernameError && this.state.username && this.state.password ) if (this.props.mode == 'login') { return ok } else if (this.props.mode == 'signup') { return ok && this.state.password2 == this.state.password && (!this.state.requireInvite || this.state.invite) } } onSubmit(e) { e.preventDefault() // don't reload the page please! if (!this.maySubmit()) { return } this.setState({loading: true}) if (this.props.mode == 'login') { this.doLogin() } else if (this.props.mode == 'signup') { this.doSignup() } } /** * Handle key presses */ onKeyPress(e) { // On "Enter", move the focus to the first mounted but unfilled field, // or submit the form if there are none. if (e.key === 'Enter') { // Ignore the @ and domain when checking the username if (this.usernameref.value.split('@')[0] === ''){ this.usernameref.focus() return } const firstUnfilledField = [ this.passwordref, this.password2ref, this.inviteref, ].find(ref => ref != null && ref.value == "") if (firstUnfilledField) { firstUnfilledField.focus() } else { this.onSubmit(e); } } } doLogin() { let account = Account.findOrAdd(this.state.username) account.login(this.state.password, this.props.autoAllowed).then( account => { this.setState({loading: false}) if (this.props.onLogin) { this.props.onLogin(account) } }, error => { if (error == "") { error = "Something failed, but we did not get a message" } this.setState({ loading: false, usernameState: 'error', passwordState: 'error', authError: error }) } ) } doSignup() { Account.create( this.state.username, this.state.password, this.state.invite ).then( account => { this.doLogin() }, error => { if (error == "") { error = "Something failed, but we did not get a message" } // error messages for invite codes are in english, and // hardcoded in leap_web in the file invite_code_validator.rb // all the code error messages have the word 'code' in them. // the api backend is returning {user: {code: "error message"}} // but that structure is lost by the time it reaches us. if (/code/.test(error)) { this.setState({ loading: false, inviteState: 'error', authError: error }) } else { this.setState({ loading: false, usernameState: 'error', passwordState: 'error', authError: error }) } } ) } } export default Login