diff --git a/app/src/api/controllers/userController.ts b/app/src/api/controllers/userController.ts index ecde5ee9..56430a02 100644 --- a/app/src/api/controllers/userController.ts +++ b/app/src/api/controllers/userController.ts @@ -5,6 +5,7 @@ import express from 'express'; import * as userService from '../services/user'; import * as passwordResetService from '../services/passwordReset'; import { TokenVerificationError } from '../services/passwordReset'; +import { ValidationError } from '../validation'; function createUser(req, res) { const user = req.body; @@ -86,6 +87,8 @@ async function resetPassword(req: express.Request, res: express.Response) { } catch (err) { if (err instanceof TokenVerificationError) { return res.send({ error: 'Could not verify token' }); + } else if (err instanceof ValidationError) { + return res.send({ error: err.message}); } throw err; diff --git a/app/src/api/services/passwordReset.ts b/app/src/api/services/passwordReset.ts index 07ede0a0..c3799d0d 100644 --- a/app/src/api/services/passwordReset.ts +++ b/app/src/api/services/passwordReset.ts @@ -5,6 +5,7 @@ import nodemailer from 'nodemailer'; import db from '../../db'; import * as userService from './user'; import { transporter } from './email'; +import { validatePassword } from '../validation'; /** @@ -24,6 +25,7 @@ export async function sendPasswordResetToken(email: string, siteOrigin: string): } export async function resetPassword(passwordResetToken: string, newPassword: string): Promise { + validatePassword(newPassword); const userId = await verifyPasswordResetToken(passwordResetToken); if (userId != undefined) { await updatePasswordForUser(userId, newPassword); diff --git a/app/src/api/services/user.ts b/app/src/api/services/user.ts index fff45010..5594e3bb 100644 --- a/app/src/api/services/user.ts +++ b/app/src/api/services/user.ts @@ -5,14 +5,18 @@ import { errors } from 'pg-promise'; import db from '../../db'; +import { validateUsername, ValidationError, validatePassword } from '../validation'; function createUser(user) { - if (!user.password || user.password.length < 8) { - return Promise.reject({ error: 'Password must be at least 8 characters' }) - } - if (user.password.length > 70) { - return Promise.reject({ error: 'Password must be at most 70 characters' }) + try { + validateUsername(user.username); + validatePassword(user.password); + } catch(err) { + if (err instanceof ValidationError) { + return Promise.reject({ error: err.message }); + } else throw err; } + return db.one( `INSERT INTO users ( diff --git a/app/src/api/validation.ts b/app/src/api/validation.ts new file mode 100644 index 00000000..e0e17437 --- /dev/null +++ b/app/src/api/validation.ts @@ -0,0 +1,25 @@ +class ValidationError extends Error { + constructor(message) { + super(message); + this.name = 'ValidationError'; + } +} + +function validateUsername(username: string): void { + if (username == undefined) throw new ValidationError('Username is required'); + if (!username.match(/^\w+$/)) throw new ValidationError('Username can only contain alphanumeric characters and underscore'); + if (username.length < 4) throw new ValidationError('Username must be at least 4 characters long'); + if (username.length > 30) throw new ValidationError('Username must be at most 30 characters long'); +} + +function validatePassword(password: string): void { + if (password == undefined) throw new ValidationError('Password is required'); + if (password.length < 8) throw new ValidationError('Password must be at least 8 characters long'); + if (password.length > 128) throw new ValidationError('Password must be at most 128 characters long'); +} + +export { + ValidationError, + validateUsername, + validatePassword +}; \ No newline at end of file diff --git a/app/src/frontend/user/signup.tsx b/app/src/frontend/user/signup.tsx index 89bc5553..a23e9ad5 100644 --- a/app/src/frontend/user/signup.tsx +++ b/app/src/frontend/user/signup.tsx @@ -94,6 +94,10 @@ class SignUp extends Component { // TODO: add proper types className="form-control" type="text" value={this.state.username} onChange={this.handleChange} placeholder="not-your-real-name" required + minLength={4} + maxLength={30} + pattern="\w+" + title="Usernames can contain only letters, numbers and the underscore" /> @@ -115,6 +119,8 @@ class SignUp extends Component { // TODO: add proper types type={(this.state.show_password)? 'text': 'password'} value={this.state.password} onChange={this.handleChange} required + minLength={8} + maxLength={128} />