diff options
| -rw-r--r-- | backend/package-lock.json | 5 | ||||
| -rw-r--r-- | backend/package.json | 1 | ||||
| -rw-r--r-- | backend/src/authn.js | 10 | ||||
| -rw-r--r-- | backend/src/index.js | 11 | ||||
| -rw-r--r-- | frontend/src/Login.js | 3 | ||||
| -rw-r--r-- | frontend/src/Register.js | 13 | ||||
| -rw-r--r-- | frontend/src/UserList.js | 43 | ||||
| -rw-r--r-- | frontend/src/axios.js | 1 |
8 files changed, 60 insertions, 27 deletions
diff --git a/backend/package-lock.json b/backend/package-lock.json index 6d7cd28..d86cb73 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1733,6 +1733,11 @@ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" }, + "owasp-password-strength-test": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/owasp-password-strength-test/-/owasp-password-strength-test-1.3.0.tgz", + "integrity": "sha1-T2KeQpA+j20nmyMNZXq2HljkSxI=" + }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", diff --git a/backend/package.json b/backend/package.json index e6b64b3..daba5f7 100644 --- a/backend/package.json +++ b/backend/package.json @@ -19,6 +19,7 @@ "js-sha512": "^0.8.0", "knex": "^0.20.8", "morgan": "^1.9.1", + "owasp-password-strength-test": "^1.3.0", "pg": "^7.18.1", "shortid": "^2.2.15" }, diff --git a/backend/src/authn.js b/backend/src/authn.js index c6c4a2e..40c7da8 100644 --- a/backend/src/authn.js +++ b/backend/src/authn.js @@ -2,6 +2,7 @@ const base64 = require('base-64'); const sha512 = require('js-sha512'); const shortid = require('shortid'); const db = require('./db'); +const owaspPw = require('owasp-password-strength-test'); const buildSessionCookie = ({ username, sessionToken, isLogout }) => { if (!isLogout) { @@ -78,6 +79,15 @@ const registerRoute = async (req, res) => { }); } + const owaspPwTestResults = owaspPw.test(password); + if (!owaspPwTestResults.strong) { + res.status(400); + return res.json({ + error: 'Password not strong enough', + errors: owaspPwTestResults.errors, + }); + } + const salt = shortid.generate(); const pwHash = sha512(salt + password); diff --git a/backend/src/index.js b/backend/src/index.js index 128ed60..6b196f3 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -6,6 +6,17 @@ const routes = require('./routes'); const app = express(); +app.use((_, res, next) => { + try { + next(); + } catch (err) { + console.error(err); + res.status(500); + res.json({ + error: 'Internal server error', + }); + } +}); app.use(morgan('dev')); app.use(bodyParser.json()); app.use(cookieParser()); diff --git a/frontend/src/Login.js b/frontend/src/Login.js index 70fff91..a251216 100644 --- a/frontend/src/Login.js +++ b/frontend/src/Login.js @@ -28,7 +28,7 @@ const Login = () => { return ( <form onSubmit={handleSubmit(onSubmit)}> - <h1 class="title">Log in</h1> + <h1 className="title">Log in</h1> <label className="label"> Username <input className="input" name="username" type="text" ref={register} required /> </label> @@ -42,7 +42,6 @@ const Login = () => { <div> No account? Register <Link to="/register">here</Link>. </div> - {/* <div className="error">{errors}</div> */} </form> ); }; diff --git a/frontend/src/Register.js b/frontend/src/Register.js index 6ba1537..6b04816 100644 --- a/frontend/src/Register.js +++ b/frontend/src/Register.js @@ -7,12 +7,11 @@ import userState from './UserState'; const Register = () => { const { register, handleSubmit } = useForm(); - const [ loginError, setLoginError ] = useState(null); + const [ data, setData ] = useState({}); const onSubmit = async data => { if (data.password !== data.confirm_password) { - setLoginError('Passwords do not match.'); - return; + return setData({ errors: [ 'Passwords do not match.' ] }); } try { const resp = await axios.post('/register', data); @@ -22,10 +21,12 @@ const Register = () => { navigate('/'); } catch (err) { if (err.response && err.response.data) { - setLoginError(err.response.data.error); + setData({ + errors: [ err.response.data.error, ...(err.response.data.errors || []) ], + }); } else { console.error(err); - setLoginError('Unknown error occurred. See console for details.'); + setData({ errors: [ 'Unknown error occurred. See console for details.' ] }); } } }; @@ -45,7 +46,7 @@ const Register = () => { <button className="button" type="submit"> Register </button> - <div className="error">{loginError}</div> + <ul className="error">{data.errors ? data.errors.map(e => <li>{e}</li>) : null}</ul> {/* <div className="error">{errors}</div> */} </form> ); diff --git a/frontend/src/UserList.js b/frontend/src/UserList.js index dd17ace..59c6425 100644 --- a/frontend/src/UserList.js +++ b/frontend/src/UserList.js @@ -1,6 +1,29 @@ import React, { useEffect, useState } from 'react'; import axios from './axios'; +const Table = ({ users }) => { + const userRows = !!users + ? users.map(user => ( + <tr key={user.id}> + <td>{user.id}</td> + <td>{user.username}</td> + </tr> + )) + : null; + + return ( + <table className="table"> + <thead> + <tr> + <th>ID</th> + <th>Username</th> + </tr> + </thead> + <tbody>{userRows}</tbody> + </table> + ); +}; + const UserList = () => { const [ data, setData ] = useState({}); useEffect(() => { @@ -22,16 +45,6 @@ const UserList = () => { })(); }, []); - const users = data.users; - const userRows = !!users - ? users.map(user => ( - <tr key={user.id}> - <td>{user.id}</td> - <td>{user.username}</td> - </tr> - )) - : null; - return ( <div> <h2 className="subtitle">User list</h2> @@ -39,15 +52,7 @@ const UserList = () => { <div className="error" key="error"> {data.error} </div> - <table className="table"> - <thead> - <tr> - <th>ID</th> - <th>Username</th> - </tr> - </thead> - <tbody>{userRows}</tbody> - </table> + {!data.error && <Table users={data.users} />} </div> </div> ); diff --git a/frontend/src/axios.js b/frontend/src/axios.js index f55b37f..2676326 100644 --- a/frontend/src/axios.js +++ b/frontend/src/axios.js @@ -3,6 +3,7 @@ import { navigate } from '@reach/router'; axios.defaults.baseURL = process.env.REACT_APP_API_BASE_URL || 'http://localhost:4000'; axios.defaults.withCredentials = true; +axios.defaults.timeout = 2000; axios.interceptors.response.use( response => { |
