# HG changeset patch # User ymh # Date 1545096442 -3600 # Node ID c78d579f4b5518645c689612e89576885afb416b # Parent f0f83f5530a61a92c73b6776b20f43a8c44efe03 Correct the registration screen. First version of a password reset screen. diff -r f0f83f5530a6 -r c78d579f4b55 client/src/actions/authActions.js --- a/client/src/actions/authActions.js Thu Dec 06 01:35:30 2018 +0100 +++ b/client/src/actions/authActions.js Tue Dec 18 02:27:22 2018 +0100 @@ -35,7 +35,14 @@ }; } - export const resetAll = () => { return { type: types.SYNC_RESET_ALL } } + +export const resetSubmit = (email) => { + return { + type: types.AUTH_RESET_SUBMIT, + email, + }; +} + diff -r f0f83f5530a6 -r c78d579f4b55 client/src/components/Login.js --- a/client/src/components/Login.js Thu Dec 06 01:35:30 2018 +0100 +++ b/client/src/components/Login.js Tue Dec 18 02:27:22 2018 +0100 @@ -41,6 +41,11 @@ this.props.history.push('/register'); } + onClickReset = (e) => { + e.preventDefault(); + this.props.history.push('/reset'); + } + renderErrorMessage(errorMessages, fieldname) { if (errorMessages && errorMessages.has(fieldname)) { return errorMessages.get(fieldname).map((message, key) => @@ -108,7 +113,9 @@

- Pas encore inscrit ? Créer un compte. + Pas encore inscrit ? Créer un compte + {/*  /  + Mot de passe oublié */}

diff -r f0f83f5530a6 -r c78d579f4b55 client/src/components/Register.js --- a/client/src/components/Register.js Thu Dec 06 01:35:30 2018 +0100 +++ b/client/src/components/Register.js Tue Dec 18 02:27:22 2018 +0100 @@ -2,18 +2,40 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import * as authActions from '../actions/authActions'; +import { Trans } from 'react-i18next'; +import * as R from 'ramda'; class Register extends Component { + constructor(props) { + super(props); + + this.state = { + username: '', + email: '', + password1: '', + password2: '' + } + + + } + register = () => { - const username = this.username.value; - const email = this.email.value; - const password1 = this.password1.value; - const password2 = this.password2.value; + const { + username, + email, + password1, + password2 } = this.state; this.props.authActions.registerSubmit(username, email, password1, password2); } + handleChange = (event) => { + const newState = {}; + newState[event.target.name] = event.target.value; + this.setState(newState); + } + submit = (e) => { e.preventDefault(); @@ -26,16 +48,44 @@ } renderErrorMessage(errorMessages, fieldname) { - if (errorMessages && errorMessages.has(fieldname)) { - return errorMessages.get(fieldname).map((message, key) => -

{ message }

+ if (errorMessages && fieldname in errorMessages) { + return errorMessages[fieldname].map((message, key) => +

{ message }

); } } + renderNonFieldErrors(errorMessages) { + + if (errorMessages && errorMessages.error) { + return ( +
+ Unable to log in. +
+ ) + } + + const errors = R.reduce( + (acc, p) => R.concat(acc, R.ifElse(Array.isArray, R.identity, v => [v,])(R.pathOr([], ['data', p], errorMessages))), + [], + ['non_field_errors', 'detail'] + ); + if (errors && errors.length > 0 ) { + return ( +
+ { errors.map((message, key) => +

{ message }

+ ) } +
+ ) + } + + } + + render() { - // const errorMessages = this.props.register.errorMessages; + const errorMessages = this.props.register.error ? this.props.register.errorMessages : false ; return (
@@ -47,34 +97,35 @@

IRI Notes

-
- - { this.username = ref; }}*/ /> - {/* { this.renderErrorMessage(errorMessages, 'username') } */} +
+ + + { errorMessages && this.renderErrorMessage(errorMessages.data, 'username') }
-
- - { this.email = ref; }}*/ /> - {/* { this.renderErrorMessage(errorMessages, 'email') } */} +
+ + + { errorMessages && this.renderErrorMessage(errorMessages.data, 'email') }
-
- - { this.password1 = ref; }}*/ /> - {/* { this.renderErrorMessage(errorMessages, 'password1') } */} +
+ + + { errorMessages && this.renderErrorMessage(errorMessages.data, 'password1') }
-
- - { this.password2 = ref; }}*/ /> - {/* { this.renderErrorMessage(errorMessages, 'password2') } */} +
+ + + { errorMessages && this.renderErrorMessage(errorMessages.data, 'password2') }
+ { this.renderNonFieldErrors(errorMessages) }
- +

- Déjà inscrit ? Se connecter. + Déjà inscrit ? Se connecter.

diff -r f0f83f5530a6 -r c78d579f4b55 client/src/components/Reset.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/src/components/Reset.js Tue Dec 18 02:27:22 2018 +0100 @@ -0,0 +1,128 @@ +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import * as authActions from '../actions/authActions'; +import { Trans } from 'react-i18next'; +import * as R from 'ramda'; + +class Reset extends Component { + + constructor(props) { + super(props); + + this.state = { + email: '' + } + + + } + + reset = () => { + const { email } = this.state; + + this.props.authActions.resetSubmit(email); + } + + handleChange = (event) => { + const newState = {}; + newState[event.target.name] = event.target.value; + this.setState(newState); + } + + submit = (e) => { + e.preventDefault(); + + this.reset(); + } + + onClickLogin = (e) => { + e.preventDefault(); + this.props.history.push('/login'); + } + + renderErrorMessage(errorMessages, fieldname) { + if (errorMessages && fieldname in errorMessages) { + return errorMessages[fieldname].map((message, key) => +

{ message }

+ ); + } + } + + renderNonFieldErrors(errorMessages) { + + if (errorMessages && errorMessages.error) { + return ( +
+ Unable to log in. +
+ ) + } + + const errors = R.reduce( + (acc, p) => R.concat(acc, R.ifElse(Array.isArray, R.identity, v => [v,])(R.pathOr([], ['data', p], errorMessages))), + [], + ['non_field_errors', 'detail'] + ); + if (errors && errors.length > 0 ) { + return ( +
+ { errors.map((message, key) => +

{ message }

+ ) } +
+ ) + } + + } + + + render() { + + const errorMessages = this.props.register.error ? this.props.register.errorMessages : false ; + + return ( +
+ {/* */} +
+
+
+
+
+

IRI Notes

+
+
+ + + { errorMessages && this.renderErrorMessage(errorMessages.data, 'email') } +
+ { this.renderNonFieldErrors(errorMessages) } +
+ +
+
+
+
+

+ Déjà inscrit ? Se connecter. +

+
+
+
+
+ ); + } +} + +function mapStateToProps(state, props) { + return { + register: state.register + }; +} + +function mapDispatchToProps(dispatch) { + return { + authActions: bindActionCreators(authActions, dispatch) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(Reset); diff -r f0f83f5530a6 -r c78d579f4b55 client/src/constants/actionTypes.js --- a/client/src/constants/actionTypes.js Thu Dec 06 01:35:30 2018 +0100 +++ b/client/src/constants/actionTypes.js Tue Dec 18 02:27:22 2018 +0100 @@ -25,6 +25,10 @@ export const AUTH_REGISTER_REQUEST = 'AUTH_REGISTER_REQUEST'; export const AUTH_REGISTER_ERROR = 'AUTH_REGISTER_ERROR'; +export const AUTH_RESET_SUBMIT = 'AUTH_RESET_SUBMIT'; +export const AUTH_RESET_REQUEST = 'AUTH_RESET_REQUEST'; +export const AUTH_RESET_ERROR = 'AUTH_RESET_ERROR'; + // Used both by login & register export const AUTH_LOGIN_SUCCESS = 'AUTH_LOGIN_SUCCESS'; diff -r f0f83f5530a6 -r c78d579f4b55 client/src/index.js --- a/client/src/index.js Thu Dec 06 01:35:30 2018 +0100 +++ b/client/src/index.js Tue Dec 18 02:27:22 2018 +0100 @@ -13,6 +13,7 @@ import CreateGroup from './components/CreateGroup'; import ReadOnlySession from './components/ReadOnlySession'; import Register from './components/Register'; +import Reset from './components/Reset'; import Settings from './components/Settings'; import './index.css'; import registerServiceWorker from './registerServiceWorker'; @@ -35,6 +36,7 @@ + diff -r f0f83f5530a6 -r c78d579f4b55 client/src/locales/en/translation.json --- a/client/src/locales/en/translation.json Thu Dec 06 01:35:30 2018 +0100 +++ b/client/src/locales/en/translation.json Tue Dec 18 02:27:22 2018 +0100 @@ -11,7 +11,8 @@ "logout_modal": "Some sessions were not saved.<1>If you continue, they will be lost." }, "login": { - "registration_message": "No account yet? Please register.", + "registration_message": "No account yet? Please register", + "reset_message": "Reset password.", "credentials_error": "Unable to log in with provided credentials.", "login_error": "Unable to log in." }, @@ -34,6 +35,15 @@ "press_enter_msg": "Press <1>Enter to add a note", "placeholder": "Type your note here..." }, + "register": { + "register_error": "Error creating the account.", + "password_confirmation": "password confirmation", + "register": "Register", + "already_registered": "Already registered ? Connect" + }, + "reset": { + "reset": "Reset password" + }, "common": { "title": "title", "description": "description", @@ -46,6 +56,7 @@ "parameters": "parameters", "username": "username", "password": "password", + "email": "email", "delete": "delete", "name": "name", "create": "create", diff -r f0f83f5530a6 -r c78d579f4b55 client/src/locales/fr/translation.json --- a/client/src/locales/fr/translation.json Thu Dec 06 01:35:30 2018 +0100 +++ b/client/src/locales/fr/translation.json Tue Dec 18 02:27:22 2018 +0100 @@ -11,7 +11,8 @@ "logout_modal": "Certaines sessions n'ont pas encore été sauvegardées.<1>Si vous continuez, elles seront perdues." }, "login": { - "registration_message": "Pas encore inscrit ? Créer un compte.", + "registration_message": "Pas encore inscrit ? Créer un compte", + "reset_message": "Mot de passe oublié.", "credentials_error": "Impossible de se connecter avec les identifiants fournis.", "login_error": "Impossible de se connecter." }, @@ -34,6 +35,15 @@ "press_enter_msg": "Appuyer sur <1>Entrée pour ajouter une note", "placeholder": "Votre espace de prise de note..." }, + "register": { + "register_error": "Erreur lors de la création du compte.", + "password_confirmation": "Confirmer le mot de passe", + "register": "S'inscrire", + "already_registered": "Déjà inscrit ? Se connecter" + }, + "reset": { + "reset": "Soumettre" + }, "common": { "title": "titre", "description": "description", @@ -46,6 +56,7 @@ "parameters": "paramètres", "username": "nom d'utilisateur", "password": "mot de passe", + "email": "e-mail", "delete": "supprimer", "name": "nom", "create": "créer", diff -r f0f83f5530a6 -r c78d579f4b55 client/src/reducers/authReducer.js --- a/client/src/reducers/authReducer.js Thu Dec 06 01:35:30 2018 +0100 +++ b/client/src/reducers/authReducer.js Tue Dec 18 02:27:22 2018 +0100 @@ -114,7 +114,7 @@ case types.AUTH_REGISTER_ERROR: return { loading: false, - success: action.type === types.AUTH_LOGIN_SUCCESS, + success: (action.type === types.AUTH_LOGIN_SUCCESS || action.type === types.AUTH_LOGIN_SUCCESS), error: action.type === types.AUTH_REGISTER_ERROR, errorMessages: action.type === types.AUTH_REGISTER_ERROR ? action.error : {} } diff -r f0f83f5530a6 -r c78d579f4b55 client/src/sagas/authSaga.js --- a/client/src/sagas/authSaga.js Thu Dec 06 01:35:30 2018 +0100 +++ b/client/src/sagas/authSaga.js Tue Dec 18 02:27:22 2018 +0100 @@ -100,6 +100,33 @@ } } +export function* watchResetSubmit() { + while (true) { + const { email } = yield take(types.AUTH_RESET_SUBMIT); + yield put({ type: types.AUTH_RESET_REQUEST, email }); + } +} + + +function* watchResetRequest(context) { + while (true) { + try { + + const { email } = yield take(types.AUTH_RESET_REQUEST); + + const client = context.client; + yield client.post('/api/auth/password/reset/', { email }); + + context.history.push('/sessions'); + + } catch(e) { + yield put({ type: types.AUTH_RESET_ERROR, error: e }); + } + } +} + + + // --- export default function* rootSaga(context) { @@ -110,5 +137,7 @@ watchRegisterRequest(context), watchStoreToken(), watchUpdateSettings(context), + watchResetSubmit(), + watchResetRequest(context), ]) } diff -r f0f83f5530a6 -r c78d579f4b55 src/.env.tmpl --- a/src/.env.tmpl Thu Dec 06 01:35:30 2018 +0100 +++ b/src/.env.tmpl Tue Dec 18 02:27:22 2018 +0100 @@ -42,8 +42,33 @@ # expiration delta for JWT tokens (in seconds) # default: 3600 -# JWT_EXPIRATION_DELTA = 3600 +# JWT_EXPIRATION_DELTA=3600 # expiration refresh delta for JWT tokens (in seconds) # default: 3600*24*7 = 604800 -# JWT_REFRESH_EXPIRATION_DELTA = 604800 +# JWT_REFRESH_EXPIRATION_DELTA=604800 + +# email backend c.f. https://docs.djangoproject.com/en/2.1/topics/email/#obtaining-an-instance-of-an-email-backend +# default: 'django.core.mail.backends.console.EmailBackend' +# EMAIL_BACKEND=django.core.mail.backends.console.EmailBackend + +# email host +# default: localhost +# EMAIL_HOST=localhost + +# email port (integer) +# default: 25 +# EMAIL_PORT=25 + +# email host user +# default: None +# EMAIL_HOST_USER= + +# email host password +# default: None +# EMAIL_HOST_PASSWORD= + +# email use TLS +# default: False +# EMAIL_USE_TLS=False + diff -r f0f83f5530a6 -r c78d579f4b55 src/irinotes/settings.py --- a/src/irinotes/settings.py Thu Dec 06 01:35:30 2018 +0100 +++ b/src/irinotes/settings.py Tue Dec 18 02:27:22 2018 +0100 @@ -284,3 +284,12 @@ ) CORS_URLS_REGEX = r'^/api/.*$' + +ACCOUNT_EMAIL_VERIFICATION = 'none' + +EMAIL_BACKEND = config('EMAIL_BACKEND', 'django.core.mail.backends.console.EmailBackend') +EMAIL_HOST = config('EMAIL_HOST', 'localhost') +EMAIL_PORT = config('EMAIL_PORT', 25, cast=int) +EMAIL_HOST_USER = config('EMAIL_HOST_USER', None) +EMAIL_HOST_PASSWORD = config('EMAIL_HSOT_PASSWORD', None) +EMAIL_USE_TLS = config('EMAIL_USE_TLS', False, cast=bool)