chore: Remove Unused Dependencies 🧹 (#939)

* chore: cleanup client depend 🧹

* chore: replace joi with zod and remove unused user validator

* chore: move dep from root to api, cleanup other unused api deps

* chore: remove unused dev dep

* chore: update bun lockfile

* fix: bun scripts

* chore: add bun flag to update script

* chore: remove legacy webpack + babel dev deps

* chore: add back dev deps needed for frontend unit testing

* fix(validators): make schemas as expected and more robust with a full test suite of edge cases

* chore: remove axios from root package, remove path from api, update bun
This commit is contained in:
Danny Avila 2023-09-14 15:12:22 -04:00 committed by GitHub
parent 7f5b0b5310
commit b3afd562b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 1935 additions and 4938 deletions

View file

@ -1,17 +1,10 @@
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const Joi = require('joi');
const DebugControl = require('../utils/debug.js');
const userSchema = require('./schema/userSchema.js');
const { SESSION_EXPIRY } = process.env ?? {};
const expires = eval(SESSION_EXPIRY) ?? 1000 * 60 * 15;
function log({ title, parameters }) {
DebugControl.log.functionName(title);
DebugControl.log.parameters(parameters);
}
userSchema.methods.toJSON = function () {
return {
id: this._id,
@ -65,26 +58,6 @@ module.exports.hashPassword = async (password) => {
return hashedPassword;
};
module.exports.validateUser = (user) => {
log({
title: 'Validate User',
parameters: [{ name: 'Validate User', value: user }],
});
const schema = {
avatar: Joi.any(),
name: Joi.string().min(3).max(80).required(),
username: Joi.string()
.trim()
.allow('')
.min(2)
.max(80)
.regex(/^[a-zA-Z0-9_.-@#$%&*() ]+$/),
password: Joi.string().min(8).max(128).allow('').allow(null),
};
return schema.validate(user);
};
const User = mongoose.model('User', userSchema);
module.exports = User;

View file

@ -24,7 +24,6 @@
"@anthropic-ai/sdk": "^0.5.4",
"@azure/search-documents": "^11.3.2",
"@dqbd/tiktoken": "^1.0.7",
"@fortaine/fetch-event-source": "^3.0.6",
"@keyv/mongo": "^2.1.8",
"@waylaidwanderer/chatgpt-api": "^1.37.2",
"axios": "^1.3.4",
@ -32,10 +31,8 @@
"cheerio": "^1.0.0-rc.12",
"cohere-ai": "^5.0.2",
"cookie": "^0.5.0",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"dotenv": "^16.0.3",
"eslint": "^8.41.0",
"express": "^4.18.2",
"express-mongo-sanitize": "^2.2.0",
"express-rate-limit": "^6.9.0",
@ -43,7 +40,6 @@
"googleapis": "^118.0.0",
"handlebars": "^4.7.7",
"html": "^1.0.0",
"joi": "^17.9.2",
"js-yaml": "^4.1.0",
"jsonwebtoken": "^9.0.0",
"keyv": "^4.5.3",
@ -63,7 +59,6 @@
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
"pino": "^8.12.1",
"sanitize": "^2.1.2",
"sharp": "^0.32.5",
"ua-parser-js": "^1.0.36",
"zod": "^3.22.2"
@ -71,7 +66,6 @@
"devDependencies": {
"jest": "^29.5.0",
"nodemon": "^3.0.1",
"path": "^0.12.7",
"supertest": "^6.3.3"
}
}

View file

@ -3,7 +3,7 @@ const bcrypt = require('bcryptjs');
const User = require('../../models/User');
const Session = require('../../models/Session');
const Token = require('../../models/schema/tokenSchema');
const { registerSchema } = require('../../strategies/validators');
const { registerSchema, errorsToString } = require('../../strategies/validators');
const config = require('../../../config/loader');
const { sendEmail } = require('../utils');
const domains = config.domains;
@ -44,15 +44,16 @@ const logoutUser = async (userId, refreshToken) => {
* @returns
*/
const registerUser = async (user) => {
const { error } = registerSchema.validate(user);
const { error } = registerSchema.safeParse(user);
if (error) {
const errorMessage = errorsToString(error.errors);
console.info(
'Route: register - Joi Validation Error',
'Route: register - Validation Error',
{ name: 'Request params:', value: user },
{ name: 'Validation error:', value: error.details },
{ name: 'Validation error:', value: errorMessage },
);
return { status: 422, message: error.details[0].message };
return { status: 422, message: errorMessage };
}
const { email, password, name, username } = user;

View file

@ -1,11 +1,11 @@
const { Strategy: PassportLocalStrategy } = require('passport-local');
const User = require('../models/User');
const { loginSchema } = require('./validators');
const { loginSchema, errorsToString } = require('./validators');
const DebugControl = require('../utils/debug.js');
async function validateLoginRequest(req) {
const { error } = loginSchema.validate(req.body);
return error ? error.details[0].message : null;
const { error } = loginSchema.safeParse(req.body);
return error ? errorsToString(error.errors) : null;
}
async function findUserByEmail(email) {

View file

@ -1,24 +1,69 @@
const Joi = require('joi');
const { z } = require('zod');
const loginSchema = Joi.object().keys({
email: Joi.string().trim().email().required(),
password: Joi.string().trim().min(8).max(128).required(),
function errorsToString(errors) {
return errors
.map((error) => {
let field = error.path.join('.');
let message = error.message;
return `${field}: ${message}`;
})
.join(' ');
}
const loginSchema = z.object({
email: z.string().email(),
password: z
.string()
.min(8)
.max(128)
.refine((value) => value.trim().length > 0, {
message: 'Password cannot be only spaces',
}),
});
const registerSchema = Joi.object().keys({
name: Joi.string().trim().min(3).max(80).required(),
username: Joi.string()
.trim()
.allow('')
.min(2)
.max(80)
.regex(/^[a-zA-Z0-9_.-@#$%&*() ]+$/),
email: Joi.string().trim().email().required(),
password: Joi.string().trim().min(8).max(128).required(),
confirm_password: Joi.string().trim().min(8).max(128).required(),
});
const registerSchema = z
.object({
name: z.string().min(3).max(80),
username: z
.union([
z.literal(''),
z
.string()
.min(2)
.max(80)
.regex(/^[a-zA-Z0-9_.-@#$%&*() ]+$/),
])
.transform((value) => (value === '' ? null : value))
.optional()
.nullable(),
email: z.string().email(),
password: z
.string()
.min(8)
.max(128)
.refine((value) => value.trim().length > 0, {
message: 'Password cannot be only spaces',
}),
confirm_password: z
.string()
.min(8)
.max(128)
.refine((value) => value.trim().length > 0, {
message: 'Password cannot be only spaces',
}),
})
.superRefine(({ confirm_password, password }, ctx) => {
if (confirm_password !== password) {
ctx.addIssue({
code: 'custom',
message: 'The passwords did not match',
});
}
});
module.exports = {
loginSchema,
registerSchema,
errorsToString,
};

View file

@ -0,0 +1,279 @@
const { loginSchema, registerSchema, errorsToString } = require('./validators');
describe('Zod Schemas', () => {
describe('loginSchema', () => {
it('should validate a correct login object', () => {
const result = loginSchema.safeParse({
email: 'test@example.com',
password: 'password123',
});
expect(result.success).toBe(true);
});
it('should invalidate an incorrect email', () => {
const result = loginSchema.safeParse({
email: 'testexample.com',
password: 'password123',
});
expect(result.success).toBe(false);
});
it('should invalidate a short password', () => {
const result = loginSchema.safeParse({
email: 'test@example.com',
password: 'pass',
});
expect(result.success).toBe(false);
});
it('should handle email with unusual characters', () => {
const emails = ['test+alias@example.com', 'test@subdomain.example.co.uk'];
emails.forEach((email) => {
const result = loginSchema.safeParse({
email,
password: 'password123',
});
expect(result.success).toBe(true);
});
});
it('should invalidate email without a domain', () => {
const result = loginSchema.safeParse({
email: 'test@.com',
password: 'password123',
});
expect(result.success).toBe(false);
});
it('should invalidate password with only spaces', () => {
const result = loginSchema.safeParse({
email: 'test@example.com',
password: ' ',
});
expect(result.success).toBe(false);
});
it('should invalidate password that is too long', () => {
const result = loginSchema.safeParse({
email: 'test@example.com',
password: 'a'.repeat(129),
});
expect(result.success).toBe(false);
});
it('should invalidate empty email or password', () => {
const result = loginSchema.safeParse({
email: '',
password: '',
});
expect(result.success).toBe(false);
});
});
describe('registerSchema', () => {
it('should validate a correct register object', () => {
const result = registerSchema.safeParse({
name: 'John Doe',
username: 'john_doe',
email: 'john@example.com',
password: 'password123',
confirm_password: 'password123',
});
expect(result.success).toBe(true);
});
it('should allow the username to be omitted', () => {
const result = registerSchema.safeParse({
name: 'John Doe',
email: 'john@example.com',
password: 'password123',
confirm_password: 'password123',
});
expect(result.success).toBe(true);
});
it('should invalidate a short name', () => {
const result = registerSchema.safeParse({
name: 'Jo',
username: 'john_doe',
email: 'john@example.com',
password: 'password123',
confirm_password: 'password123',
});
expect(result.success).toBe(false);
});
it('should handle empty username by transforming to null', () => {
const result = registerSchema.safeParse({
name: 'John Doe',
username: '',
email: 'john@example.com',
password: 'password123',
confirm_password: 'password123',
});
expect(result.success).toBe(true);
expect(result.data.username).toBe(null);
});
it('should handle name with special characters', () => {
const names = ['Jöhn Dœ', 'John <Doe>'];
names.forEach((name) => {
const result = registerSchema.safeParse({
name,
username: 'john_doe',
email: 'john@example.com',
password: 'password123',
confirm_password: 'password123',
});
expect(result.success).toBe(true);
});
});
it('should handle username with special characters', () => {
const usernames = ['john.doe@', 'john..doe'];
usernames.forEach((username) => {
const result = registerSchema.safeParse({
name: 'John Doe',
username,
email: 'john@example.com',
password: 'password123',
confirm_password: 'password123',
});
expect(result.success).toBe(true);
});
});
it('should invalidate mismatched password and confirm_password', () => {
const result = registerSchema.safeParse({
name: 'John Doe',
username: 'john_doe',
email: 'john@example.com',
password: 'password123',
confirm_password: 'password124',
});
expect(result.success).toBe(false);
});
it('should handle email without a TLD', () => {
const result = registerSchema.safeParse({
name: 'John Doe',
username: 'john_doe',
email: 'john@domain',
password: 'password123',
confirm_password: 'password123',
});
expect(result.success).toBe(false);
});
it('should handle email with multiple @ symbols', () => {
const result = registerSchema.safeParse({
name: 'John Doe',
username: 'john_doe',
email: 'john@domain@com',
password: 'password123',
confirm_password: 'password123',
});
expect(result.success).toBe(false);
});
it('should handle name that is too long', () => {
const result = registerSchema.safeParse({
name: 'a'.repeat(81),
username: 'john_doe',
email: 'john@example.com',
password: 'password123',
confirm_password: 'password123',
});
expect(result.success).toBe(false);
});
it('should handle username that is too long', () => {
const result = registerSchema.safeParse({
name: 'John Doe',
username: 'a'.repeat(81),
email: 'john@example.com',
password: 'password123',
confirm_password: 'password123',
});
expect(result.success).toBe(false);
});
it('should handle password or confirm_password that is too long', () => {
const result = registerSchema.safeParse({
name: 'John Doe',
username: 'john_doe',
email: 'john@example.com',
password: 'a'.repeat(129),
confirm_password: 'a'.repeat(129),
});
expect(result.success).toBe(false);
});
it('should handle password or confirm_password that is just spaces', () => {
const result = registerSchema.safeParse({
name: 'John Doe',
username: 'john_doe',
email: 'john@example.com',
password: ' ',
confirm_password: ' ',
});
expect(result.success).toBe(false);
});
it('should handle null values for fields', () => {
const result = registerSchema.safeParse({
name: null,
username: null,
email: null,
password: null,
confirm_password: null,
});
expect(result.success).toBe(false);
});
it('should handle undefined values for fields', () => {
const result = registerSchema.safeParse({
name: undefined,
username: undefined,
email: undefined,
password: undefined,
confirm_password: undefined,
});
expect(result.success).toBe(false);
});
it('should handle extra fields not defined in the schema', () => {
const result = registerSchema.safeParse({
name: 'John Doe',
username: 'john_doe',
email: 'john@example.com',
password: 'password123',
confirm_password: 'password123',
extraField: 'I shouldn\'t be here',
});
expect(result.success).toBe(true);
});
});
describe('errorsToString', () => {
it('should convert errors to string', () => {
const { error } = registerSchema.safeParse({
name: 'Jo',
username: 'john_doe',
email: 'john@example.com',
password: 'password123',
confirm_password: 'password123',
});
const result = errorsToString(error.errors);
expect(result).toBe('name: String must contain at least 3 character(s)');
});
});
});