From af06ddc06d4321eafa6020b8781c9be5095335f3 Mon Sep 17 00:00:00 2001 From: Kayne Ruse Date: Wed, 28 Jul 2021 23:01:41 +1000 Subject: [PATCH] Working on password recovery --- client/components/app.jsx | 3 + client/components/pages/recover.jsx | 91 +++++++++++++++++++++++++++++ client/components/pages/reset.jsx | 91 +++++++++++++++++++++++++++++ configure-script.js | 2 + package-lock.json | 72 ++++++++++++++++++++++- package.json | 1 + 6 files changed, 257 insertions(+), 3 deletions(-) create mode 100644 client/components/pages/recover.jsx create mode 100644 client/components/pages/reset.jsx diff --git a/client/components/app.jsx b/client/components/app.jsx index b3527c1..394d8f7 100644 --- a/client/components/app.jsx +++ b/client/components/app.jsx @@ -29,6 +29,9 @@ const App = props => { import('./pages/login')} /> import('./pages/account')} /> + import('./pages/recover')} /> + import('./pages/reset')} /> + import('./pages/admin')} /> import('./pages/mod')} /> diff --git a/client/components/pages/recover.jsx b/client/components/pages/recover.jsx new file mode 100644 index 0000000..98619ee --- /dev/null +++ b/client/components/pages/recover.jsx @@ -0,0 +1,91 @@ +import React, { useContext, useRef } from 'react'; +import { Redirect } from 'react-router-dom'; + +import { TokenContext } from '../utilities/token-provider'; + +//utilities +const validateEmail = require('../../../common/utilities/validate-email'); + +const Recover = props => { + //context + const authTokens = useContext(TokenContext); + + //misplaced? + if (authTokens.accessToken) { + return ; + } + + //refs + const emailRef = useRef(); + const recoverRef = useRef(); + + return ( +
+

Recover Password

+
{ //on submit + recoverRef.current.disabled = true; + evt.preventDefault(); + const [result, redirect] = await handleSubmit(emailRef.current.value); + if (result) { + alert(result); + recoverRef.current.disabled = false; + } + + //redirect + if (redirect) { + props.history.push('/'); + } + } + }> +
+ + +
+ + +
+
+ ); +}; + +const handleSubmit = async (email) => { + email = email.trim(); + + const err = handleValidation(email); + + if (err) { + return [err]; + } + + //send to the auth server + const result = await fetch(`${process.env.AUTH_URI}/auth/recover`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + }, + body: JSON.stringify({ + email + }) + }); + + if (!result.ok) { + const err = `${result.status}: ${await result.text()}`; + console.error(err); + return [err, false]; + } + + return [await result.text(), true]; +}; + +//returns an error message, or null on success +const handleValidation = (email) => { + if (!validateEmail(email)) { + return 'invalid email'; + } + + return null; +}; + +export default Recover; \ No newline at end of file diff --git a/client/components/pages/reset.jsx b/client/components/pages/reset.jsx new file mode 100644 index 0000000..598c01f --- /dev/null +++ b/client/components/pages/reset.jsx @@ -0,0 +1,91 @@ +import React, { useContext, useRef } from 'react'; +import { Redirect } from 'react-router-dom'; +import queryString from 'query-string'; + +import { TokenContext } from '../utilities/token-provider'; + +const Reset = props => { + //context + const authTokens = useContext(TokenContext); + + //query + query = queryString.parse(props.location.search); + + //misplaced? + if (authTokens.accessToken || !query.email || !query.token) { + return ; + } + + //refs + const passwordRef = useRef(); + const retypeRef = useRef(); + const resetRef = useRef(); + + //render the thing + return ( +
+

Reset Password

+
{ + evt.preventDefault(); + const [err] = await update(passwordRef.current.value, retypeRef.current.value, query); + + if (err) { + alert(err); + return; + } + + alert('Details updated'); + + //redirect + if (redirect) { + props.history.push('/'); + } + }}> +
+
+ + +
+ +
+ + +
+
+ + +
+ + +
+ ); +}; + +const update = async (password, retype, query) => { + if (password != retype) { + return ['Passwords do not match']; + } + + if (password && password.length < 8) { + return ['Password is too short']; + } + + const result = await fetch(`${process.env.AUTH_URI}/auth/reset?email=${query.email}&token=${query.token}`, { + method: 'PATCH', + headers: { + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + password: password ? password : null, + }) + }); + + if (!result.ok) { + return [`${await result.status}: ${await result.text()}`]; + } else { + return [null]; + } +} + +export default Reset; \ No newline at end of file diff --git a/configure-script.js b/configure-script.js index 28265d0..577de79 100644 --- a/configure-script.js +++ b/configure-script.js @@ -62,6 +62,7 @@ See https://github.com/krgamestudios/MERN-template/wiki for help. //auth configuration const authName = await question('Auth Name', 'auth'); const authWebAddress = await question('Auth Web Address', `${authName}.${projectWebAddress}`); + const authResetAddress = await question('Auth Reset Addr', `${projectWebAddress}/reset`); const authDBUser = await question('Auth DB Username', authName); const authDBPass = await question('Auth DB Password', 'charizard'); @@ -179,6 +180,7 @@ services: environment: - WEB_PROTOCOL=https - WEB_ADDRESS=${authWebAddress} + - WEB_RESET_ADDRESS=${authResetAddress} - WEB_PORT=${authPort} - DB_HOSTNAME=database - DB_DATABASE=${authName} diff --git a/package-lock.json b/package-lock.json index e3d666f..0bbebe6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "html-webpack-plugin": "^5.3.2", "jwt-decode": "^3.1.2", "mariadb": "^2.5.4", + "query-string": "^7.0.1", "raw-loader": "^4.0.2", "react": "^17.0.2", "react-dom": "^17.0.2", @@ -3662,7 +3663,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true, "engines": { "node": ">=0.10" } @@ -4606,6 +4606,14 @@ "node": ">=8" } }, + "node_modules/filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs=", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", @@ -7608,6 +7616,23 @@ "node": ">=0.6" } }, + "node_modules/query-string": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.0.1.tgz", + "integrity": "sha512-uIw3iRvHnk9to1blJCG3BTc+Ro56CBowJXKmNNAm3RulvPBzWLRqKSiiDk+IplJhsydwtuNMHi8UGQFcCLVfkA==", + "dependencies": { + "decode-uri-component": "^0.2.0", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/querystring": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", @@ -9013,6 +9038,14 @@ "wbuf": "^1.7.3" } }, + "node_modules/split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "engines": { + "node": ">=6" + } + }, "node_modules/split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -9135,6 +9168,14 @@ "node": ">= 0.6" } }, + "node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=", + "engines": { + "node": ">=4" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -13941,8 +13982,7 @@ "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" }, "decompress-response": { "version": "3.3.0", @@ -14698,6 +14738,11 @@ "to-regex-range": "^5.0.1" } }, + "filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs=" + }, "finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", @@ -16959,6 +17004,17 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, + "query-string": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.0.1.tgz", + "integrity": "sha512-uIw3iRvHnk9to1blJCG3BTc+Ro56CBowJXKmNNAm3RulvPBzWLRqKSiiDk+IplJhsydwtuNMHi8UGQFcCLVfkA==", + "requires": { + "decode-uri-component": "^0.2.0", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + } + }, "querystring": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", @@ -18106,6 +18162,11 @@ "wbuf": "^1.7.3" } }, + "split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==" + }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -18204,6 +18265,11 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, + "strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=" + }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", diff --git a/package.json b/package.json index 36ee7dd..faf2692 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "html-webpack-plugin": "^5.3.2", "jwt-decode": "^3.1.2", "mariadb": "^2.5.4", + "query-string": "^7.0.1", "raw-loader": "^4.0.2", "react": "^17.0.2", "react-dom": "^17.0.2",