2020-06-05 12:25:26 -07:00
import React from 'react' ;
import { connect } from 'react-redux' ;
2020-06-05 14:24:07 -07:00
import { defineMessages , injectIntl , FormattedDate } from 'react-intl' ;
2020-06-05 12:25:26 -07:00
import ImmutablePureComponent from 'react-immutable-pure-component' ;
import PropTypes from 'prop-types' ;
2020-06-05 13:43:03 -07:00
import ImmutablePropTypes from 'react-immutable-proptypes' ;
2020-06-05 12:25:26 -07:00
import Column from '../ui/components/column' ;
import {
SimpleForm ,
SimpleInput ,
FieldsGroup ,
TextInput ,
} from 'soapbox/features/forms' ;
2020-06-05 13:43:03 -07:00
import {
changeEmail ,
changePassword ,
fetchOAuthTokens ,
2020-06-05 13:54:09 -07:00
revokeOAuthToken ,
2020-07-20 13:23:38 -07:00
deleteAccount ,
2020-06-05 13:43:03 -07:00
} from 'soapbox/actions/auth' ;
2020-06-05 12:39:27 -07:00
import { showAlert } from 'soapbox/actions/alerts' ;
2020-06-05 12:25:26 -07:00
2020-06-27 16:21:06 -07:00
/ *
Security settings page for user account
Routed to / auth / edit
Includes following features :
- Change Email
- Change Password
- Sessions
- Deactivate Account
* /
2020-06-05 12:25:26 -07:00
const messages = defineMessages ( {
heading : { id : 'column.security' , defaultMessage : 'Security' } ,
submit : { id : 'security.submit' , defaultMessage : 'Save changes' } ,
2020-06-05 12:39:27 -07:00
updateEmailSuccess : { id : 'security.update_email.success' , defaultMessage : 'Email successfully updated.' } ,
updateEmailFail : { id : 'security.update_email.fail' , defaultMessage : 'Update email failed.' } ,
2020-06-05 13:14:27 -07:00
updatePasswordSuccess : { id : 'security.update_password.success' , defaultMessage : 'Password successfully updated.' } ,
updatePasswordFail : { id : 'security.update_password.fail' , defaultMessage : 'Update password failed.' } ,
2020-06-05 13:21:29 -07:00
emailFieldLabel : { id : 'security.fields.email.label' , defaultMessage : 'Email address' } ,
passwordFieldLabel : { id : 'security.fields.password.label' , defaultMessage : 'Password' } ,
oldPasswordFieldLabel : { id : 'security.fields.old_password.label' , defaultMessage : 'Current password' } ,
newPasswordFieldLabel : { id : 'security.fields.new_password.label' , defaultMessage : 'New password' } ,
confirmationFieldLabel : { id : 'security.fields.password_confirmation.label' , defaultMessage : 'New password (again)' } ,
2020-06-05 14:24:07 -07:00
revoke : { id : 'security.tokens.revoke' , defaultMessage : 'Revoke' } ,
emailHeader : { id : 'security.headers.update_email' , defaultMessage : 'Change Email' } ,
passwordHeader : { id : 'security.headers.update_password' , defaultMessage : 'Change Password' } ,
tokenHeader : { id : 'security.headers.tokens' , defaultMessage : 'Sessions' } ,
2020-07-20 13:23:38 -07:00
deleteHeader : { id : 'security.headers.delete' , defaultMessage : 'Delete Account' } ,
deleteText : { id : 'security.text.delete' , defaultMessage : 'To delete your account, enter your password then click Delete Account. This is a permanent action that cannot be undone. Your account will be destroyed from this server, and a deletion request will be sent to other servers. It\'s not guaranteed that all servers will purge your account.' } ,
deleteSubmit : { id : 'security.submit.delete' , defaultMessage : 'Delete Account' } ,
deleteAccountSuccess : { id : 'security.delete_account.success' , defaultMessage : 'Account successfully deleted.' } ,
deleteAccountFail : { id : 'security.delete_account.fail' , defaultMessage : 'Account deletion failed.' } ,
2020-06-05 12:25:26 -07:00
} ) ;
2020-06-05 13:03:21 -07:00
export default @ injectIntl
2020-06-05 13:00:24 -07:00
class SecurityForm extends ImmutablePureComponent {
2020-06-05 12:25:26 -07:00
2020-06-05 14:24:07 -07:00
static propTypes = {
dispatch : PropTypes . func . isRequired ,
intl : PropTypes . object . isRequired ,
} ;
2020-06-05 13:03:21 -07:00
render ( ) {
const { intl } = this . props ;
return (
< Column icon = 'lock' heading = { intl . formatMessage ( messages . heading ) } backBtnSlim >
< ChangeEmailForm / >
2020-06-05 13:14:27 -07:00
< ChangePasswordForm / >
2020-06-05 13:43:03 -07:00
< AuthTokenList / >
2020-06-27 17:55:24 -07:00
< DeactivateAccount / >
2020-06-05 13:03:21 -07:00
< / C o l u m n >
) ;
}
}
@ connect ( )
@ injectIntl
class ChangeEmailForm extends ImmutablePureComponent {
2020-06-05 12:25:26 -07:00
static propTypes = {
email : PropTypes . string ,
dispatch : PropTypes . func . isRequired ,
intl : PropTypes . object . isRequired ,
} ;
2020-06-05 12:39:27 -07:00
state = {
email : '' ,
password : '' ,
2020-06-05 12:50:24 -07:00
isLoading : false ,
2020-06-05 12:39:27 -07:00
}
2020-06-05 12:25:26 -07:00
handleInputChange = e => {
this . setState ( { [ e . target . name ] : e . target . value } ) ;
}
handleSubmit = e => {
const { email , password } = this . state ;
2020-06-05 12:39:27 -07:00
const { dispatch , intl } = this . props ;
2020-06-05 12:50:24 -07:00
this . setState ( { isLoading : true } ) ;
return dispatch ( changeEmail ( email , password ) ) . then ( ( ) => {
2020-06-05 12:45:00 -07:00
this . setState ( { email : '' , password : '' } ) ; // TODO: Maybe redirect user
2020-06-05 12:39:27 -07:00
dispatch ( showAlert ( '' , intl . formatMessage ( messages . updateEmailSuccess ) ) ) ;
} ) . catch ( error => {
2020-06-05 12:45:00 -07:00
this . setState ( { password : '' } ) ;
2020-06-05 12:39:27 -07:00
dispatch ( showAlert ( '' , intl . formatMessage ( messages . updateEmailFail ) ) ) ;
2020-06-05 12:50:24 -07:00
} ) . then ( ( ) => {
this . setState ( { isLoading : false } ) ;
2020-06-05 12:39:27 -07:00
} ) ;
2020-06-05 12:25:26 -07:00
}
render ( ) {
const { intl } = this . props ;
return (
2020-06-05 13:03:21 -07:00
< SimpleForm onSubmit = { this . handleSubmit } >
2020-06-05 14:24:07 -07:00
< h2 > { intl . formatMessage ( messages . emailHeader ) } < / h 2 >
2020-06-05 13:03:21 -07:00
< fieldset disabled = { this . state . isLoading } >
< FieldsGroup >
< TextInput
2020-06-05 13:21:29 -07:00
label = { intl . formatMessage ( messages . emailFieldLabel ) }
2020-06-05 13:03:21 -07:00
placeholder = 'me@example.com'
name = 'email'
onChange = { this . handleInputChange }
value = { this . state . email }
/ >
< SimpleInput
type = 'password'
2020-06-05 13:21:29 -07:00
label = { intl . formatMessage ( messages . passwordFieldLabel ) }
2020-06-05 13:03:21 -07:00
name = 'password'
onChange = { this . handleInputChange }
value = { this . state . password }
/ >
< div className = 'actions' >
< button name = 'button' type = 'submit' className = 'btn button button-primary' >
{ intl . formatMessage ( messages . submit ) }
< / b u t t o n >
< / d i v >
< / F i e l d s G r o u p >
< / f i e l d s e t >
< / S i m p l e F o r m >
2020-06-05 12:25:26 -07:00
) ;
}
}
2020-06-05 13:14:27 -07:00
@ connect ( )
@ injectIntl
class ChangePasswordForm extends ImmutablePureComponent {
static propTypes = {
dispatch : PropTypes . func . isRequired ,
intl : PropTypes . object . isRequired ,
} ;
state = {
oldPassword : '' ,
newPassword : '' ,
confirmation : '' ,
isLoading : false ,
}
handleInputChange = e => {
this . setState ( { [ e . target . name ] : e . target . value } ) ;
}
clearForm = ( ) => {
this . setState ( { oldPassword : '' , newPassword : '' , confirmation : '' } ) ;
}
handleSubmit = e => {
const { oldPassword , newPassword , confirmation } = this . state ;
const { dispatch , intl } = this . props ;
this . setState ( { isLoading : true } ) ;
return dispatch ( changePassword ( oldPassword , newPassword , confirmation ) ) . then ( ( ) => {
this . clearForm ( ) ; // TODO: Maybe redirect user
dispatch ( showAlert ( '' , intl . formatMessage ( messages . updatePasswordSuccess ) ) ) ;
} ) . catch ( error => {
this . clearForm ( ) ;
dispatch ( showAlert ( '' , intl . formatMessage ( messages . updatePasswordFail ) ) ) ;
} ) . then ( ( ) => {
this . setState ( { isLoading : false } ) ;
} ) ;
}
render ( ) {
const { intl } = this . props ;
return (
< SimpleForm onSubmit = { this . handleSubmit } >
2020-06-05 14:24:07 -07:00
< h2 > { intl . formatMessage ( messages . passwordHeader ) } < / h 2 >
2020-06-05 13:14:27 -07:00
< fieldset disabled = { this . state . isLoading } >
< FieldsGroup >
< SimpleInput
type = 'password'
2020-06-05 13:21:29 -07:00
label = { intl . formatMessage ( messages . oldPasswordFieldLabel ) }
2020-06-05 13:14:27 -07:00
name = 'oldPassword'
onChange = { this . handleInputChange }
value = { this . state . oldPassword }
/ >
< SimpleInput
type = 'password'
2020-06-05 13:21:29 -07:00
label = { intl . formatMessage ( messages . newPasswordFieldLabel ) }
2020-06-05 13:14:27 -07:00
name = 'newPassword'
onChange = { this . handleInputChange }
value = { this . state . newPassword }
/ >
< SimpleInput
type = 'password'
2020-06-05 13:21:29 -07:00
label = { intl . formatMessage ( messages . confirmationFieldLabel ) }
2020-06-05 13:14:27 -07:00
name = 'confirmation'
onChange = { this . handleInputChange }
value = { this . state . confirmation }
/ >
< div className = 'actions' >
< button name = 'button' type = 'submit' className = 'btn button button-primary' >
{ intl . formatMessage ( messages . submit ) }
< / b u t t o n >
< / d i v >
< / F i e l d s G r o u p >
< / f i e l d s e t >
< / S i m p l e F o r m >
) ;
}
}
2020-06-05 13:43:03 -07:00
const mapStateToProps = state => ( {
tokens : state . getIn ( [ 'auth' , 'tokens' ] ) ,
} ) ;
@ connect ( mapStateToProps )
@ injectIntl
class AuthTokenList extends ImmutablePureComponent {
static propTypes = {
2020-06-05 14:24:07 -07:00
dispatch : PropTypes . func . isRequired ,
intl : PropTypes . object . isRequired ,
2020-06-05 13:43:03 -07:00
tokens : ImmutablePropTypes . list ,
2020-06-05 14:24:07 -07:00
} ;
2020-06-05 13:43:03 -07:00
2020-06-05 13:54:09 -07:00
handleRevoke = id => {
return e => {
this . props . dispatch ( revokeOAuthToken ( id ) ) ;
} ;
}
2020-06-05 13:43:03 -07:00
componentDidMount ( ) {
this . props . dispatch ( fetchOAuthTokens ( ) ) ;
}
render ( ) {
2020-06-05 14:24:07 -07:00
const { tokens , intl } = this . props ;
if ( tokens . isEmpty ( ) ) return null ;
2020-06-05 13:43:03 -07:00
return (
< SimpleForm >
2020-06-05 14:24:07 -07:00
< h2 > { intl . formatMessage ( messages . tokenHeader ) } < / h 2 >
< div className = 'authtokens' >
{ tokens . reverse ( ) . map ( ( token , i ) => (
< div key = { i } className = 'authtoken' >
< div className = 'authtoken__app-name' > { token . get ( 'app_name' ) } < / d i v >
< div className = 'authtoken__valid-until' >
< FormattedDate
value = { new Date ( token . get ( 'valid_until' ) ) }
hour12 = { false }
year = 'numeric'
month = 'short'
day = '2-digit'
hour = '2-digit'
minute = '2-digit'
/ >
< / d i v >
< div className = 'authtoken__revoke' >
< button onClick = { this . handleRevoke ( token . get ( 'id' ) ) } >
{ this . props . intl . formatMessage ( messages . revoke ) }
< / b u t t o n >
< / d i v >
2020-06-05 13:54:09 -07:00
< / d i v >
2020-06-05 14:24:07 -07:00
) ) }
< / d i v >
2020-06-05 13:43:03 -07:00
< / S i m p l e F o r m >
) ;
}
}
2020-06-27 17:55:24 -07:00
@ connect ( mapStateToProps )
@ injectIntl
class DeactivateAccount extends ImmutablePureComponent {
static propTypes = {
dispatch : PropTypes . func . isRequired ,
intl : PropTypes . object . isRequired ,
} ;
state = {
password : '' ,
isLoading : false ,
}
handleInputChange = e => {
this . setState ( { [ e . target . name ] : e . target . value } ) ;
}
handleSubmit = e => {
const { password } = this . state ;
const { dispatch , intl } = this . props ;
this . setState ( { isLoading : true } ) ;
2020-07-20 13:23:38 -07:00
return dispatch ( deleteAccount ( password ) ) . then ( ( ) => {
2020-06-27 17:55:24 -07:00
//this.setState({ email: '', password: '' }); // TODO: Maybe redirect user
2020-07-20 13:23:38 -07:00
dispatch ( showAlert ( '' , intl . formatMessage ( messages . deleteAccountSuccess ) ) ) ;
2020-06-27 17:55:24 -07:00
} ) . catch ( error => {
this . setState ( { password : '' } ) ;
2020-07-20 13:23:38 -07:00
dispatch ( showAlert ( '' , intl . formatMessage ( messages . deleteAccountFail ) ) ) ;
2020-06-27 17:55:24 -07:00
} ) . then ( ( ) => {
this . setState ( { isLoading : false } ) ;
} ) ;
}
render ( ) {
const { intl } = this . props ;
return (
< SimpleForm onSubmit = { this . handleSubmit } >
2020-07-20 13:23:38 -07:00
< h2 > { intl . formatMessage ( messages . deleteHeader ) } < / h 2 >
2020-06-27 17:55:24 -07:00
< p className = 'hint' >
2020-07-20 13:23:38 -07:00
{ intl . formatMessage ( messages . deleteText ) }
2020-06-27 17:55:24 -07:00
< / p >
< fieldset disabled = { this . state . isLoading } >
< FieldsGroup >
< SimpleInput
type = 'password'
label = { intl . formatMessage ( messages . passwordFieldLabel ) }
name = 'password'
onChange = { this . handleInputChange }
value = { this . state . password }
/ >
< div className = 'actions' >
< button name = 'button' type = 'submit' className = 'btn button button-primary' >
2020-07-20 13:23:38 -07:00
{ intl . formatMessage ( messages . deleteSubmit ) }
2020-06-27 17:55:24 -07:00
< / b u t t o n >
< / d i v >
< / F i e l d s G r o u p >
< / f i e l d s e t >
< / S i m p l e F o r m >
) ;
}
}