Skip to content

Commit

Permalink
Adding input error highlighting
Browse files Browse the repository at this point in the history
  • Loading branch information
hgorges committed Aug 24, 2024
1 parent 430153d commit c5d31cb
Show file tree
Hide file tree
Showing 23 changed files with 358 additions and 87 deletions.
5 changes: 5 additions & 0 deletions public/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ p {
border-left: 4px solid var(--error-color);
}

.invalid {
/* HACK !important */
border-color: red !important;
}

@media (max-width: 768px) {
#page-content {
height: calc(100svh - 3.5rem - 2rem);
Expand Down
File renamed without changes.
File renamed without changes.
20 changes: 19 additions & 1 deletion src/controller/loginPage.ts → src/controller/login.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { NextFunction, Request, Response } from 'express-serve-static-core';
import userModel from '../models/userModel';
import { ValidationError } from '../utils/utils';

export async function renderLogin(
req: Request,
res: Response,
_next: NextFunction,
renderOptions: {
statusCode?: number;
errors?: ValidationError[];
username?: string;
password?: string;
} = {},
Expand All @@ -24,6 +26,7 @@ export async function renderLogin(
errorMessage: errorMessage.length > 0 ? errorMessage[0] : null,
username: '',
password: '',
errors: [],
...renderOptions,
});
}
Expand All @@ -43,7 +46,22 @@ export async function login(
const user = await userModel.getUserForLogin(username, password);
if (!user) {
req.flash('error', 'Invalid username or password!');
renderLogin(req, res, next, { statusCode: 422, ...req.body });
renderLogin(req, res, next, {
statusCode: 422,
errors: [
{
param: 'username',
message: 'Invalid username or password!',
value: username,
},
{
param: 'password',
message: 'Invalid username or password!',
value: password,
},
],
...req.body,
});
return;
}

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import userModel from '../models/userModel';
import mailer from '../config/mailer';
import { renderFile } from 'ejs';
import path from 'path';
import { ValidationError } from '../utils/utils';

export async function renderPasswordChange(
req: Request,
res: Response,
_next: NextFunction,
renderOptions: {
statusCode?: number;
errors?: ValidationError[];
password?: string;
confirm_password?: string;
} = {},
Expand All @@ -34,6 +36,7 @@ export async function renderPasswordChange(
errorMessage: errorMessage.length > 0 ? errorMessage[0] : null,
password: '',
confirm_password: '',
errors: [],
...renderOptions,
});
}
Expand All @@ -49,7 +52,22 @@ export async function changePassword(
if (password !== confirm_password) {
req.flash('error', 'Passwords do not match!');
req.params.passwordResetToken = passwordResetToken;
renderPasswordChange(req, res, next, { statusCode: 422, ...req.body });
renderPasswordChange(req, res, next, {
statusCode: 422,
errors: [
{
param: 'password',
message: 'Passwords do not match!',
value: password,
},
{
param: 'confirm_password',
message: 'Passwords do not match!',
value: confirm_password,
},
],
...req.body,
});
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import userModel from '../models/userModel';
import mailer from '../config/mailer';
import { renderFile } from 'ejs';
import path from 'path';
import { ValidationError } from '../utils/utils';

export async function renderPasswordReset(
req: Request,
res: Response,
_next: NextFunction,
renderOptions: {
statusCode?: number;
errors?: ValidationError[];
email?: string;
} = {},
): Promise<void> {
Expand All @@ -25,6 +27,7 @@ export async function renderPasswordReset(
infoMessage: infoMessage.length > 0 ? infoMessage[0] : null,
errorMessage: errorMessage.length > 0 ? errorMessage[0] : null,
email: '',
errors: [],
...renderOptions,
});
}
Expand Down
36 changes: 32 additions & 4 deletions src/controller/settingsPage.ts → src/controller/settings.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { NextFunction, Request, Response } from 'express-serve-static-core';
import userModel from '../models/userModel';
import { ValidationError } from '../utils/utils';

export async function renderSettingsPage(
req: Request,
res: Response,
_next: NextFunction,
renderOptions: {
statusCode?: number;
errors?: ValidationError[];
username?: string;
first_name?: string;
last_name?: string;
Expand Down Expand Up @@ -40,6 +42,7 @@ export async function renderSettingsPage(
work_longitude: user?.work_gps.y,
password: '',
confirm_password: '',
errors: [],
...renderOptions,
});
}
Expand Down Expand Up @@ -67,13 +70,38 @@ export async function saveSettings(

if (password !== confirm_password) {
req.flash('error', 'Passwords do not match!');
renderSettingsPage(req, res, next, { statusCode: 422, ...req.body });
renderSettingsPage(req, res, next, {
statusCode: 422,
errors: [
{
param: 'password',
message: 'Passwords do not match!',
value: password,
},
{
param: 'confirm_password',
message: 'Passwords do not match!',
value: confirm_password,
},
],
...req.body,
});
return;
}

if (password == null || password.length <= 7) {
req.flash('error', 'Password must be at least 8 characters long!');
renderSettingsPage(req, res, next, { statusCode: 422, ...req.body });
if (password != null && password.trim() !== '' && password.length <= 7) {
req.flash('error', 'Password must be at least s8 characters long!');
renderSettingsPage(req, res, next, {
statusCode: 422,
errors: [
{
param: 'password',
message: 'Password must be at least 8 characters long!',
value: password,
},
],
...req.body,
});
return;
}

Expand Down
56 changes: 52 additions & 4 deletions src/controller/signupPage.ts → src/controller/signup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import userModel from '../models/userModel';
import mailer from '../config/mailer';
import { renderFile } from 'ejs';
import path from 'path';
import { ValidationError } from '../utils/utils';

export async function renderSignup(
req: Request,
res: Response,
_next: NextFunction,
renderOptions: {
statusCode?: number;
errors?: ValidationError[];
username?: string;
first_name?: string;
last_name?: string;
Expand All @@ -32,6 +34,7 @@ export async function renderSignup(
email: '',
password: '',
confirm_password: '',
errors: [],
...renderOptions,
});
}
Expand All @@ -53,23 +56,68 @@ export async function signup(
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
if (!emailRegex.test(email)) {
req.flash('error', 'Valid email is required!');
renderSignup(req, res, next, { statusCode: 422, ...req.body });
renderSignup(req, res, next, {
statusCode: 422,
errors: [
{
param: 'email',
message: 'Valid email is required!',
value: email,
},
],
...req.body,
});
return;
}

if (await userModel.getUserByUsername(username)) {
req.flash('error', 'Username is already taken!');
renderSignup(req, res, next, { statusCode: 422, ...req.body });
renderSignup(req, res, next, {
statusCode: 422,
errors: [
{
param: 'username',
message: 'Username is already taken!',
value: username,
},
],
...req.body,
});
return;
}
if (await userModel.getUserByEmail(email)) {
req.flash('error', 'Email is already taken!');
renderSignup(req, res, next, { statusCode: 422, ...req.body });
renderSignup(req, res, next, {
statusCode: 422,
errors: [
{
param: 'email',
message: 'Email is already taken!',
value: email,
},
],
...req.body,
});
return;
}
if (password !== confirm_password) {
req.flash('error', 'Passwords do not match!');
renderSignup(req, res, next, { statusCode: 422, ...req.body });
renderSignup(req, res, next, {
statusCode: 422,
errors: [
{
param: 'password',
message: 'Passwords do not match!',
value: password,
},
{
param: 'confirm_password',
message: 'Passwords do not match!',
value: confirm_password,
},
],
...req.body,
});
return;
}

Expand Down
8 changes: 4 additions & 4 deletions src/routes/sessionRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import validatePasswordChange from '../validators/validatePasswordChange';
import {
changePassword,
renderPasswordChange,
} from '../controller/passwordChangePage';
} from '../controller/passwordChange';
import {
createPasswordResetToken,
renderPasswordReset,
} from '../controller/passwordResetPage';
import { login, renderLogin } from '../controller/loginPage';
import { renderSignup, signup } from '../controller/signupPage';
} from '../controller/passwordReset';
import { login, renderLogin } from '../controller/login';
import { renderSignup, signup } from '../controller/signup';

const sessionRouter = express.Router();

Expand Down
8 changes: 4 additions & 4 deletions src/routes/userRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import {
redirectFromGoogle,
redirectToGoogle,
} from '../middleware/google-auth';
import { renderSettingsPage, saveSettings } from '../controller/settingsPage';
import { renderSettingsPage, saveSettings } from '../controller/settings';
import cors from 'cors';
import {
completeTodo,
postponeTodo,
renderDashboardPage,
switchLocation,
} from '../controller/dashboardPage';
import { renderAdminPage } from '../controller/adminPage';
import { renderNotFoundPage } from '../controller/notFoundPage';
} from '../controller/dashboard';
import { renderAdminPage } from '../controller/admin';
import { renderNotFoundPage } from '../controller/notFound';
import validateEmptyBody from '../validators/validateEmptyBody';
import validateSettings from '../validators/validateSettings';

Expand Down
28 changes: 16 additions & 12 deletions src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ export type AuthSession = Session & {
googleCalendarAccessToken?: string;
};

export type ValidationError = {
param: string;
message: string | undefined;
value: any;
};

export const fileRoot = path.join(__dirname, '..', '..');

export function validate(
Expand All @@ -28,27 +34,25 @@ export function validate(
): void {
const isValid = validateFunction(req.body);
if (!isValid && validateFunction.errors) {
const error = parseErrors(validateFunction.errors);
req.flash('error', error.map((e) => e.message).join(', '));
renderFunction(req, res, next, { statusCode: 422, ...req.body });
const errors = parseErrors(validateFunction.errors);
req.flash('error', errors.map((error) => error.message).join(', '));
renderFunction(req, res, next, {
statusCode: 422,
errors,
...req.body,
});
return;
}
next();
}

function parseErrors(validationErrors: ErrorObject[]): any[] {
const errors: any[] = [];
const errors: ValidationError[] = [];
validationErrors.forEach((error) => {
errors.push({
param:
error.params['missingProperty'] !== undefined
? error.params['missingProperty']
: error.instancePath,
param: error.instancePath.replace(/[./]+/g, ''),
message: error.message,
value:
error.params['missingProperty'] !== undefined
? null
: error.data,
value: error.data,
});
});
return errors;
Expand Down
2 changes: 1 addition & 1 deletion src/validators/validateEmptyBody.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import ajv from '../config/ajv';
import { JSONSchemaType } from 'ajv';
import { NextFunction, Request, Response } from 'express-serve-static-core';
import { validate } from '../utils/utils';
import { renderLogin } from '../controller/loginPage';
import { renderLogin } from '../controller/login';

const emptyBodySchema: JSONSchemaType<{
_csrf: string;
Expand Down
Loading

0 comments on commit c5d31cb

Please sign in to comment.