diff --git a/client/src/components/AccountForm/index.js b/client/src/components/AccountForm/index.js index 79a0ffc..55bb3a6 100644 --- a/client/src/components/AccountForm/index.js +++ b/client/src/components/AccountForm/index.js @@ -1,6 +1,6 @@ import { h, Component, render } from 'preact'; import style from './style.css'; -import { handleSubmit, clearForms, setStateUserOrRedirectToSignIn } from "../../js/utilities"; +import { validateAccountForm, clearForms } from "../../js/validate-account-form"; import { LOGIN_PATH, REGISTER_PATH, RESET_PATH } from '../../../config'; import linkState from "linkstate"; import { route } from 'preact-router'; @@ -8,102 +8,101 @@ import { route } from 'preact-router'; export default class AccountForm extends Component { constructor() { super(); - this.state = { - form_message: "", - successMessageMap: this.createMessageMap(), - matchPasswordsMap: this.createMatchPasswordsMap(), - validatePasswordMap: this.createValidatePasswordMap(), - }; - this.handleSubmit = handleSubmit.bind(this); - this.routeToRegister = this.routeToRegister.bind(this); - } - createMessageMap = () => { - const messageMap = new Map; - messageMap.set(LOGIN_PATH, 'You have signed in.'); - messageMap.set(REGISTER_PATH, 'You have created an account.'); - messageMap.set(RESET_PATH, 'You have changed your password.'); - return messageMap; + this.routeToRegister = this.routeToRegister.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); } - createMatchPasswordsMap = () => { - const matchPasswordsMap = new Map; - matchPasswordsMap.set(REGISTER_PATH, ['password','confirm_password']); - matchPasswordsMap.set(RESET_PATH, ['new_password','confirm_password']); - return matchPasswordsMap; - } + /** + * TODO - refactor: simplify + * + * Pass: event, path + * Return: a promise - request result that can be used to here to set 'successMessage' + * via setState + */ + handleSubmit = (event) => { + event.preventDefault(); - createValidatePasswordMap = () => { - const validatePasswordMap = new Map; - validatePasswordMap.set(LOGIN_PATH, 'password'); - validatePasswordMap.set(REGISTER_PATH, 'password'); - validatePasswordMap.set(RESET_PATH, 'new_password'); - return validatePasswordMap; - } + const formData = { + name: this.state.name, + email: this.state.email, + password: this.state.password, + new_password: this.state.new_password, + confirm_password: this.state.confirm_password, + } - doSubmit = (event) => { const args = { - event: event, path: this.props.path, - message_key: 'form_message', - component: this, - matchPasswordFields: this.state.matchPasswordsMap.get(this.props.path), - passwordToValidate: this.state.validatePasswordMap.get(this.props.path), - successMessage: this.getSuccessMessage(), - }; - this.handleSubmit(args); - } + formData, + } - getSuccessMessage = () => { - return this.state.successMessageMap.get(this.props.path); - } + validateAccountForm(args).then((response) => { + console.log('validateAccountForm(): ', response); + if (response.status) { + if (this.props.path === RESET_PATH) { + // TODO - alert with success + console.log('path: ', this.props.path); + alert(response.message); + } else { // redirect to /profile with success for login/registration + console.log('route to profile'); + route(`/profile`, true); + } + } else { + // TODO - alert failure to process + alert(response.message); + } + }).catch(function (error) { + alert(error); + }); + }; componentWillUnmount = () => { clearForms(); } componentDidMount = () => { - if (this.props.path === RESET_PATH) { - setStateUserOrRedirectToSignIn(this); - } + // TODO - remove + console.log('AccountForm.componentDidMount()'); } routeToRegister() { route("/register", true); } - render({path},{ form_message, user, name, email, password, new_password, confirm_password }) { + render({ path },{ name, email, password, new_password, confirm_password }) { //DEFAULT TO LOGIN_PATH let display =
-
- + + + value={password} onInput={linkState(this, 'password')} required/>
-
- OR -
-
+
+
+ OR +
+ +

forgot password?

; if(path === REGISTER_PATH){ display =
-
+ - + + value={password} onInput={linkState(this, 'password')} required/> @@ -114,7 +113,7 @@ export default class AccountForm extends Component { if(path === RESET_PATH){ display =
- +

To change user info:

@@ -122,7 +121,7 @@ export default class AccountForm extends Component { value={email} onInput={linkState(this, 'email')}/> -
+

To change password:

@@ -137,7 +136,6 @@ export default class AccountForm extends Component { return (
Navi logo -
{form_message}
{display}
); diff --git a/client/src/components/ProfileCard/index.js b/client/src/components/ProfileCard/index.js index fa2ef9a..933d1da 100644 --- a/client/src/components/ProfileCard/index.js +++ b/client/src/components/ProfileCard/index.js @@ -1,6 +1,6 @@ import {h, Component} from 'preact'; import style from './style'; -import {setStateUserOrRedirectToSignIn} from "../../js/utilities"; +import {setStateUserOrRedirectToSignIn} from "../../js/validate-account-form"; export default class ProfileCard extends Component { constructor() { diff --git a/client/src/js/server-requests-utils.js b/client/src/js/server-requests-utils.js index 507c63f..4202e0b 100644 --- a/client/src/js/server-requests-utils.js +++ b/client/src/js/server-requests-utils.js @@ -98,18 +98,3 @@ export const makeRequest = (method='GET', baseEndPoint, endPointAddon='', bodyDa return axios.request(config); } - - -/* -Incomplete Function... - -import { url } from 'inspector'; - -const AXIOS_INSTANCE = axios.create({ - baseURL: API_SERVER -}); - -exports.postAutocomplete = (input='') => { //ERR: url is read only - // return AXIOS_INSTANCE.post(url=BASE_ENDPOINTS.autocomplete, {input}); -} - */ \ No newline at end of file diff --git a/client/src/js/utilities.js b/client/src/js/utilities.js deleted file mode 100644 index 475283e..0000000 --- a/client/src/js/utilities.js +++ /dev/null @@ -1,129 +0,0 @@ -import {route} from "preact-router"; -import {makeRequest, token} from './server-requests-utils'; - -const getSignInPromise = () => { - return makeRequest('GET','user'); -} - -const formDataForAxios = (form) => { - let formData = {}; - for (var pair of new FormData(form).entries()) { - if(!pair[1]) return null; - formData[pair[0]] = pair[1]; - } - return formData; -} - -const setStateValue = (key, value, component) => { - const stateObject = {}; - stateObject[key] = value; - component.setState(stateObject); -} - -const validEmail = (email) => { - const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - return re.test(String(email).toLowerCase()); -}; - -const validPassword = (password) => { - const re = /^(?=.*[0-9])(?=.*[!@#$%^&*])[a-zA-Z0-9!@#$%^&*]{6,}$/; - return re.test(String(password)); -}; - -const passwordsMatch = (password1, password2) => { - return password1 === password2; -} - -/** - * Checks whether any of the fields are empty - * Checks whether confirm_password matches password - * Checks whether password is of minimum 6 characters & that it has atleast one number, - * one letter, & atleast one specail character. - * Checks whether the email address is of a valid format - */ -const formIsValid = (args) => { - let {message_key, formData, component, matchPasswordFields, passwordToValidate} = args; - - if(!formData){ - setStateValue(message_key, 'One or more fields were left empty', component); - return false; - } - - if(matchPasswordFields){ - const [password1, password2] = matchPasswordFields; - if (!passwordsMatch(formData[password1],formData[password2])) { - setStateValue(message_key, 'Passwords do no match.', component); - return false; - } - } - - if (!validPassword(formData[passwordToValidate])) { - setStateValue(message_key, - 'Password should have minimum length of 6 & it should have atleast one letter, one number, and one special character', component); - return false; - } - - if (!validEmail(formData.email)) { - setStateValue(message_key, 'Email is not of the valid format', component); - return false; - } - - return true; -} - -// Exports below -export const handleSubmit = (args) => { - let {event, path, message_key, component, matchPasswordFields, passwordToValidate, successMessage} = args; - - event.preventDefault(); - - const formData = formDataForAxios(event.target); - const validationArgs = { - message_key: message_key, - formData: formData, - component: component, - matchPasswordFields: matchPasswordFields, - passwordToValidate: passwordToValidate, - }; - if(!formIsValid(validationArgs)) return; - - makeRequest('POST', path, '', formData) - .then(function (response) { - token.setCookie(response.data.token); - route(`/profile?success=${successMessage}`,true); - }) - .catch(function (error) { - if (error.response === undefined) { - return setStateValue(message_key, error, component); - } - if (error.response.status === 401) { - token.deleteCookie(); - return setStateValue(message_key, 'Wrong password.', component); - } else { - return setStateValue(message_key, error.response.data, component); - } - }); -} - -export const clearForms = () => { - for (let form of document.getElementsByTagName("form")) { - form.reset(); - } -} - -export const logout = () => { - token.deleteCookie(); -} - -export const setStateUserOrRedirectToSignIn = (component) => { - getSignInPromise() - .then((response) => { - component.setState({ - user: response.data, - isSignedIn: true, - }); - } - ).catch(() => { - route('/signin', true); - }); -} diff --git a/client/src/js/validate-account-form.js b/client/src/js/validate-account-form.js new file mode 100644 index 0000000..c818442 --- /dev/null +++ b/client/src/js/validate-account-form.js @@ -0,0 +1,167 @@ +import {route} from 'preact-router'; +import {makeRequest, token} from './server-requests-utils'; +import { LOGIN_PATH, REGISTER_PATH, RESET_PATH } from '../../config'; +import { resolve } from 'url'; + +const validEmail = (email) => { + const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return re.test(String(email).toLowerCase()); +}; + +const validPassword = (password) => { + const re = /^(?=.*[0-9])(?=.*[!@#$%^&*])[a-zA-Z0-9!@#$%^&*]{6,}$/; + return re.test(String(password)); +}; + +const passwordsMatch = (password1, password2) => { + return password1 === password2; +}; + +const validateReset = (formData) => { + const {name, email, password, new_password, confirm_password} = formData; + + // passwords form fields are required so if any is null then user info update + if (password && new_password && confirm_password) { + // validate passwords + if (!validPassword(password) || !validPassword(new_password) || + !validPassword(confirm_password)) { + return { + status: false, + message: 'Password should have atleast 6 characts, should have atleast one letter, number, and special character', + body: null, + }; + } else if (!passwordsMatch(new_password, confirm_password)) { + return { + status: false, + message: 'Try again, new passwords don\'t match!', + body: null, + }; + } else { + return { + status: true, + message: 'Success - valid request!', + body: {password, new_password, confirm_password}, + }; + } + } + + // validate user info has at least on field completed + if (name.length < 1 && email.length < 1) { + return { + status: false, + message: 'Please enter a new name and/or email address!', + body: null, + }; + } + + return { + status: false, + message: 'Success - valid request!', + body: {name, email}, + }; +}; + +const validateLogin = (formData) => { + const {email, password} = formData; + + if (!validPassword(password)) { + return { + status: false, + message: 'Password should have atleast 6 characts, should have atleast one letter, number, and special character', + body: null, + }; + } + + return { + status: true, + message: 'Success - valid request!', + body: {email, password}, + }; +}; + +const validateRegister = (formData) => { + const {name, email, password, confirm_password} = formData; + + if (!validPassword(password) || !validPassword(confirm_password)) { + return { + status: false, + message: 'Password should have atleast 6 characts, should have atleast one letter, number, and special character', + body: null, + }; + } else if (!passwordsMatch(password, confirm_password)) { + return { + status: false, + message: 'Try again, passwords don\'t match!', + body: null, + }; + } else { + return { + status: true, + message: 'Success - valid request!', + body: {name, email, password, confirm_password}, + }; + } +}; + +// Exports below +export const validateAccountForm = (args) => { + + const {path, formData} = args; + let result = null; + + if (path === RESET_PATH) { + result = validateReset(formData); + } + + if (path === REGISTER_PATH) { + result = validateRegister(formData); + } + + if (path == LOGIN_PATH) { + result = validateLogin(formData); + } + + const {status, message, body} = result; + + // return if validation fails + if (!status) { + return new Promise((resolve, reject) => { + resolve({status, message}); + }); + } + + // otherwise process server request + return new Promise((resolve, reject) => { + makeRequest('POST', path, '', body) + .then(function (response) { + token.setCookie(response.data.token); + resolve({status, message}); + }) + .catch(function (error) { + reject(error); + }); + }); +}; + +export const clearForms = () => { + for (let form of document.getElementsByTagName("form")) { + form.reset(); + } +}; + +export const logout = () => { + token.deleteCookie(); +}; + +export const setStateUserOrRedirectToSignIn = (component) => { + return makeRequest('GET','user') + .then((response) => { + component.setState({ + user: response.data, + isSignedIn: true, + }); + } + ).catch(() => { + route('/signin', true); + }); +} diff --git a/client/src/routes/profile/index.js b/client/src/routes/profile/index.js index 3f302af..2cda778 100644 --- a/client/src/routes/profile/index.js +++ b/client/src/routes/profile/index.js @@ -7,7 +7,7 @@ import ProfileEditForm from '../../components/ProfileEditForm'; import ProfileSettingsForm from '../../components/ProfileSettingsForm'; import SavedPinsCard from '../../components/SavedPinsCard'; import SearchHistoryCard from '../../components/SearchHistoryCard'; -import {setStateUserOrRedirectToSignIn} from "../../js/utilities"; +import {setStateUserOrRedirectToSignIn} from "../../js/validate-account-form"; export default class Profile extends Component { diff --git a/client/src/routes/signout/index.js b/client/src/routes/signout/index.js index 817e131..c647f44 100644 --- a/client/src/routes/signout/index.js +++ b/client/src/routes/signout/index.js @@ -1,5 +1,5 @@ import { h, Component } from 'preact'; -import { logout } from "../../js/utilities"; +import { logout } from "../../js/validate-account-form"; import Home from '../home'; export default class SignOut extends Component { diff --git a/controllers/users-controller.js b/controllers/users-controller.js index 95876a2..9b74bb0 100644 --- a/controllers/users-controller.js +++ b/controllers/users-controller.js @@ -54,7 +54,8 @@ exports.registerUser = (appReq, appRes) => { // encrypt password const HASHED_PASSWORD = bcrypt.hashSync(appReq.body.password, 8); - //This is inside User.count so that it does not run before the User.count check finishes + // This is inside User.count so that it does not run before the User.count + // check finishes User.create( { name: appReq.body.name, @@ -103,6 +104,17 @@ exports.getUser = (appReq, appRes) => { ); }; +const checkPassword = (password1, password2) => { + return bcrypt.compareSync(password1, password2); +}; + +const getInvalidPasswordResponse = (appRes) => { + return appRes.status(401).send({ + auth: false, + token: null, + }); +}; + /** * @description Handles user login * @@ -119,13 +131,15 @@ exports.loginUser = (appReq, appRes) => { User.findOne({ email: appReq.body.email }, (err, user) => { if (err) return appRes.status(500).send('Error on the server.'); if (!user) return appRes.status(404).send('No user found.'); - if(!checkPassword(appReq.body.password,user.password)) return getInvalidPasswordResponse(appRes); + if (!checkPassword(appReq.body.password, user.password)) { + return getInvalidPasswordResponse(appRes); + } const token = jwt.sign({ id: user._id }, JWT_KEY, { expiresIn: 86400, }); - appRes.status(200).send({ + return appRes.status(200).send({ auth: true, token, }); @@ -156,19 +170,19 @@ exports.logoutUser = (appReq, appRes) => { * @apiError 404 {request error} User not found. * @apiError 500 {server error} Problem finding user. * - * @param {string} appReq.body.email - email provided by user - * @param {string} appReq.body.password - user provided password - * @param {string} appReq.body.new_password - user provided password + * @param {string} appReq.body.password - current user password + * @param {string} appReq.body.new_password - user provided new password * @param {string} appReq.body.confirm_password - user provided password */ exports.resetPassword = (appReq, appRes) => { - const HASHED_PASSWORD = bcrypt.hashSync(appReq.body.new_password, 8); - User.findOne({ email: appReq.body.email }, (err, user) => { + User.findById(appReq.userId, (err, user) => { if (err) return appRes.status(500).send('Error on the server.'); if (!user) return appRes.status(404).send('No user found.'); - if(!checkPassword(appReq.body.password,user.password)) return getInvalidPasswordResponse(appRes); + if (!checkPassword(appReq.body.password, user.password)) { + return getInvalidPasswordResponse(appRes); + } user.password = HASHED_PASSWORD; user.save(function (err, updatedUser) { @@ -187,13 +201,18 @@ exports.resetPassword = (appReq, appRes) => { }; -const checkPassword = (password1, password2) => { - return bcrypt.compareSync(password1, password2); -} - -const getInvalidPasswordResponse = (appRes) => { - return appRes.status(401).send({ - auth: false, - token: null, - }); -} \ No newline at end of file +/** + * @description Handles updating user info + * + * @api {POST} /users/update + * @apiSuccess 200 {auth: true, token: token} jsonwebtoken. + * @apiError 401 {auth: false, token: null} Invalid password. + * @apiError 404 {request error} User not found. + * @apiError 500 {server error} Problem finding user. + * + * @param {string} appReq.body.name - name provided by user + * @param {string} appReq.body.email - email provided by user + */ +exports.update = (appReq, appRes) => { + appRes.status(200).send('Test123'); +}; diff --git a/routes/search.js b/routes/search.js index 24f337b..7cc2861 100644 --- a/routes/search.js +++ b/routes/search.js @@ -38,6 +38,7 @@ router.get('/places/:id', placeDetails); router.post('/autocomplete', autocomplete); router.get('/textsearch', textSearch); router.post('/textsearch', textSearch); + /** * Saved search history end points */ diff --git a/routes/users.js b/routes/users.js index ac38a55..b65b74d 100644 --- a/routes/users.js +++ b/routes/users.js @@ -9,7 +9,7 @@ const router = express.Router(); */ /** * @description Handle requests to users main end point - * + * * @api {GET} /users * @return {success: false, error: err} Not a valid end point */ @@ -20,10 +20,14 @@ router.get('/', (req, res) => { }); }); +/** + * Valid Users endpoints + */ router.post('/register', usersController.registerUser); router.get('/user', verifyToken, usersController.getUser); router.post('/login', usersController.loginUser); router.get('/logout', usersController.logoutUser); -router.post('/reset-password', usersController.resetPassword); +router.post('/reset-password', verifyToken, usersController.resetPassword); +router.post('/update', verifyToken, usersController.update); module.exports = router; diff --git a/server.js b/server.js index 7839207..59b271b 100644 --- a/server.js +++ b/server.js @@ -9,4 +9,4 @@ server.listen(config.PORT, () => { console.info(`Server is running at ${config.HOST}:${config.PORT}`); }); -module.exports = server; \ No newline at end of file +module.exports = server;