added more user test, improved validation, and added validation to login screen
This commit is contained in:
@@ -6,6 +6,7 @@ import authLogout from "../middleware/authLogout";
|
||||
import {
|
||||
changePasswordValidationRules,
|
||||
createAccountValidationRules,
|
||||
loginAccountValidationRules,
|
||||
} from "../middleware/user/user-middleware";
|
||||
|
||||
const userController = new UserController();
|
||||
@@ -20,7 +21,11 @@ router.get("/user-service/user-detailed", auth, userController.getUserDetailed);
|
||||
|
||||
// POST
|
||||
|
||||
router.post("/user-service/login", userController.login);
|
||||
router.post(
|
||||
"/user-service/login",
|
||||
loginAccountValidationRules,
|
||||
userController.login
|
||||
);
|
||||
|
||||
router.post("/user-service/logout", authLogout, userController.logout);
|
||||
|
||||
|
||||
@@ -9,13 +9,17 @@ export const changePasswordValidationRules = [
|
||||
.exists()
|
||||
.withMessage("Old password is required")
|
||||
.isString()
|
||||
.withMessage("Old password must be a string"),
|
||||
.withMessage("Old password must be a string")
|
||||
.isLength({ min: 6, max: 256 })
|
||||
.withMessage(
|
||||
"Old password must be at least 6 characters and at most 256 characters"
|
||||
),
|
||||
body("newPassword")
|
||||
.exists()
|
||||
.withMessage("New password is required")
|
||||
.isString()
|
||||
.withMessage("New password must be a string")
|
||||
.isLength({ min: 1, max: 256 })
|
||||
.isLength({ min: 6, max: 256 })
|
||||
.withMessage(
|
||||
"New password must be at least 6 characters and at most 256 characters"
|
||||
),
|
||||
@@ -30,7 +34,34 @@ export const createAccountValidationRules = [
|
||||
.withMessage("Email is required")
|
||||
.isString()
|
||||
.withMessage("Email must be a string")
|
||||
.isLength({ min: 1, max: 256 })
|
||||
.isLength({ min: 3, max: 320 })
|
||||
.withMessage(
|
||||
"Email must be at least 3 characters and at most 320 characters"
|
||||
)
|
||||
.isEmail()
|
||||
.withMessage("Email is invalid"),
|
||||
body("password")
|
||||
.exists()
|
||||
.withMessage("Password is required")
|
||||
.isString()
|
||||
.withMessage("Password must be a string")
|
||||
.isLength({ min: 6, max: 256 })
|
||||
.withMessage(
|
||||
"Password must be at least 6 characters and at most 256 characters"
|
||||
),
|
||||
middlewareValidationFunction,
|
||||
];
|
||||
|
||||
export const loginAccountValidationRules = [
|
||||
body("email")
|
||||
.exists()
|
||||
.withMessage("Email is required")
|
||||
.isString()
|
||||
.withMessage("Email must be a string")
|
||||
.isLength({ min: 3, max: 320 })
|
||||
.withMessage(
|
||||
"Email must be at least 3 characters and at most 320 characters"
|
||||
)
|
||||
.isEmail()
|
||||
.withMessage("Email is invalid"),
|
||||
body("password")
|
||||
|
||||
@@ -17,6 +17,10 @@ const userSchema = new mongoose.Schema(
|
||||
validate(value: any): any {
|
||||
if (!validator.isEmail(value)) {
|
||||
throw new Error("Email is invalid");
|
||||
} else if (value.length > 320) {
|
||||
throw new Error("Email length must be less than 320 characters");
|
||||
} else if (value.length < 3) {
|
||||
throw new Error("Email length must be at least 3 characters");
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -26,7 +30,9 @@ const userSchema = new mongoose.Schema(
|
||||
required: true,
|
||||
validate(value: any): any {
|
||||
if (value.length < 6) {
|
||||
throw new Error("Password Length Not Sufficent");
|
||||
throw new Error("Password length must be at least 6 characters");
|
||||
} else if (value.length > 256) {
|
||||
throw new Error("Password length must be less than 256 characters");
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -10,6 +10,7 @@ import sendVerificationEmail from "../../utils/sendVerificationEmail";
|
||||
import sendPasswordResetEmail from "../../utils/sendPasswordResetEmail";
|
||||
import ForbiddenError from "../../utils/ForbiddenError";
|
||||
import ConflictError from "../../utils/ConflictError";
|
||||
import NotAuthorizedError from "../../utils/NotAuthorizedError";
|
||||
|
||||
type UserDataType = {
|
||||
email: string;
|
||||
@@ -131,13 +132,13 @@ class UserService {
|
||||
) => {
|
||||
const user = await User.findById(userID);
|
||||
|
||||
if (!user) throw new NotFoundError("Could Not Find User");
|
||||
if (!user) throw new NotAuthorizedError("User information is incorrect");
|
||||
|
||||
const date = new Date();
|
||||
|
||||
const isMatch = await bcrypt.compare(oldPassword, user.password);
|
||||
|
||||
if (!isMatch) throw new ForbiddenError("Old Password Is Incorrect");
|
||||
if (!isMatch) throw new NotAuthorizedError("User information is incorrect");
|
||||
|
||||
const encryptionKey = user.getEncryptionKey();
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import Spinner from "../Spinner/Spinner";
|
||||
import { toast, ToastContainer } from "react-toastify";
|
||||
import Swal from "sweetalert2";
|
||||
import { AxiosError } from "axios";
|
||||
import isEmail from "validator/es/lib/isEmail";
|
||||
|
||||
const LoginPage = () => {
|
||||
const [email, setEmail] = useState("");
|
||||
@@ -56,7 +57,10 @@ const LoginPage = () => {
|
||||
navigate("/home");
|
||||
setLoadingLogin(false);
|
||||
} catch (e) {
|
||||
if (e instanceof AxiosError && e.response?.status === 401) {
|
||||
if (
|
||||
e instanceof AxiosError &&
|
||||
[400, 401].includes(e.response?.status || 0)
|
||||
) {
|
||||
setError("Incorrect email or password");
|
||||
} else {
|
||||
setError("Login Error");
|
||||
@@ -82,6 +86,8 @@ const LoginPage = () => {
|
||||
} catch (e) {
|
||||
if (e instanceof AxiosError && e.response?.status === 409) {
|
||||
setError("Email Already Exists");
|
||||
} else if (e instanceof AxiosError && e.response?.status === 400) {
|
||||
setError("Validation Error");
|
||||
} else {
|
||||
setError("Create Account Error");
|
||||
}
|
||||
@@ -162,12 +168,42 @@ const LoginPage = () => {
|
||||
if (mode === "login" || mode === "reset") return "";
|
||||
|
||||
if (mode === "create") {
|
||||
if (password !== verifyPassword) return "Passwords do not match";
|
||||
if (password.length) {
|
||||
if (password.length < 6) {
|
||||
return "Password must be at least 6 characters";
|
||||
} else if (password.length > 256) {
|
||||
return "Password must be less than 256 characters";
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
password.length &&
|
||||
verifyPassword.length &&
|
||||
password !== verifyPassword
|
||||
) {
|
||||
return "Passwords do not match";
|
||||
}
|
||||
|
||||
if (email.length) {
|
||||
const isValidEmail = isEmail(email);
|
||||
|
||||
if (email.length < 3) {
|
||||
return "Email must be at least 3 characters";
|
||||
} else if (email.length > 320) {
|
||||
return "Email must be less than 320 characters";
|
||||
} else if (!isValidEmail) {
|
||||
return "Email is invalid";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
})();
|
||||
|
||||
useEffect(() => {
|
||||
setError("");
|
||||
}, [email.length, password.length, verifyPassword.length]);
|
||||
|
||||
useEffect(() => {
|
||||
const loggedIn = window.localStorage.getItem("hasPreviouslyLoggedIn");
|
||||
if (loggedIn === "true") {
|
||||
|
||||
@@ -228,7 +228,7 @@ describe("File Controller", () => {
|
||||
.set("Cookie", authToken)
|
||||
.send({
|
||||
id: file._id,
|
||||
title: "a" * 257,
|
||||
title: "a".repeat(257),
|
||||
});
|
||||
|
||||
expect(fileResponse.status).toBe(400);
|
||||
|
||||
@@ -175,7 +175,7 @@ describe("File Controller", () => {
|
||||
.post(`/folder-service/create`)
|
||||
.set("Cookie", authToken)
|
||||
.send({
|
||||
name: "a" * 257,
|
||||
name: "a".repeat(257),
|
||||
parent: "/",
|
||||
});
|
||||
|
||||
@@ -316,7 +316,7 @@ describe("File Controller", () => {
|
||||
.set("Cookie", authToken)
|
||||
.send({
|
||||
id: folder._id,
|
||||
title: "a" * 257,
|
||||
title: "a".repeat(257),
|
||||
});
|
||||
|
||||
expect(folderResponse.status).toBe(400);
|
||||
|
||||
@@ -12,8 +12,6 @@ const { ObjectId } = require("mongodb");
|
||||
let mongoServer;
|
||||
let authToken;
|
||||
let authToken2;
|
||||
let file;
|
||||
let file2;
|
||||
let user;
|
||||
let user2;
|
||||
|
||||
@@ -29,6 +27,7 @@ describe("File Controller", () => {
|
||||
await mongoose.model("fs.files").deleteMany({});
|
||||
await mongoose.model("Folder").deleteMany({});
|
||||
await mongoose.model("User").deleteMany({});
|
||||
envFileFix(env);
|
||||
|
||||
user = await request(app)
|
||||
.post("/user-service/create")
|
||||
@@ -77,6 +76,7 @@ describe("File Controller", () => {
|
||||
expect(userResponse.status).toBe(401);
|
||||
});
|
||||
});
|
||||
|
||||
describe("User login: POST /user-service/login", () => {
|
||||
test("Should login user", async () => {
|
||||
const userResponse = await request(app).post("/user-service/login").send({
|
||||
@@ -102,7 +102,52 @@ describe("File Controller", () => {
|
||||
});
|
||||
expect(userResponse.status).toBe(401);
|
||||
});
|
||||
test("Should return 400 if email length is less than 3", async () => {
|
||||
const userResponse = await request(app).post("/user-service/login").send({
|
||||
email: "ab",
|
||||
password: "test1234",
|
||||
});
|
||||
|
||||
expect(userResponse.status).toBe(400);
|
||||
});
|
||||
test("Should return 400 if email length is greater than 320", async () => {
|
||||
const userResponse = await request(app)
|
||||
.post("/user-service/login")
|
||||
.send({
|
||||
email: "a".repeat(321) + "@test.com",
|
||||
password: "test1234",
|
||||
});
|
||||
|
||||
expect(userResponse.status).toBe(400);
|
||||
});
|
||||
test("Should return 400 if email address is invalid", async () => {
|
||||
const userResponse = await request(app).post("/user-service/login").send({
|
||||
email: "a@b",
|
||||
password: "test1234",
|
||||
});
|
||||
|
||||
expect(userResponse.status).toBe(400);
|
||||
});
|
||||
test("Should return 400 if password length is less than 6", async () => {
|
||||
const userResponse = await request(app).post("/user-service/login").send({
|
||||
email: "test@test.com",
|
||||
password: "a",
|
||||
});
|
||||
|
||||
expect(userResponse.status).toBe(400);
|
||||
});
|
||||
test("Should return 400 if password length is greater than 256", async () => {
|
||||
const userResponse = await request(app)
|
||||
.post("/user-service/login")
|
||||
.send({
|
||||
email: "test@test.com",
|
||||
password: "a".repeat(257),
|
||||
});
|
||||
|
||||
expect(userResponse.status).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Create User: POST /user-service/create", () => {
|
||||
test("Should create user", async () => {
|
||||
const userResponse = await request(app)
|
||||
@@ -138,21 +183,21 @@ describe("File Controller", () => {
|
||||
|
||||
expect(userResponse.status).toBe(400);
|
||||
});
|
||||
test("Should return 400 if email length is less than 1", async () => {
|
||||
test("Should return 400 if email length is less than 3", async () => {
|
||||
const userResponse = await request(app)
|
||||
.post("/user-service/create")
|
||||
.send({
|
||||
email: "",
|
||||
email: "a@b",
|
||||
password: "test1234",
|
||||
});
|
||||
|
||||
expect(userResponse.status).toBe(400);
|
||||
});
|
||||
test("Should return 400 if email length is greater than 256", async () => {
|
||||
test("Should return 400 if email length is greater than 320", async () => {
|
||||
const userResponse = await request(app)
|
||||
.post("/user-service/create")
|
||||
.send({
|
||||
email: "a" * 257,
|
||||
email: "a".repeat(321) + "@test.com",
|
||||
password: "test1234",
|
||||
});
|
||||
|
||||
@@ -173,7 +218,7 @@ describe("File Controller", () => {
|
||||
.post("/user-service/create")
|
||||
.send({
|
||||
email: "newuser@test.com",
|
||||
password: "a" * 257,
|
||||
password: "a".repeat(267),
|
||||
});
|
||||
|
||||
expect(userResponse.status).toBe(400);
|
||||
@@ -201,4 +246,177 @@ describe("File Controller", () => {
|
||||
expect(userResponse.status).toBe(403);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Change Password: PATCH /user-service/change-password", () => {
|
||||
test("Should change password", async () => {
|
||||
const changePasswordResponse = await request(app)
|
||||
.patch("/user-service/change-password")
|
||||
.set("Cookie", authToken)
|
||||
.send({
|
||||
oldPassword: "test1234",
|
||||
newPassword: "test12345",
|
||||
});
|
||||
expect(changePasswordResponse.status).toBe(200);
|
||||
const loginResponse = await request(app)
|
||||
.post("/user-service/login")
|
||||
.send({
|
||||
email: user.body.user.email,
|
||||
password: "test12345",
|
||||
});
|
||||
expect(loginResponse.status).toBe(200);
|
||||
});
|
||||
test("Should return 401 if not authorized", async () => {
|
||||
const changePasswordResponse = await request(app)
|
||||
.patch("/user-service/change-password")
|
||||
.set("Cookie", "access-token=test")
|
||||
.send({
|
||||
oldPassword: "test1234",
|
||||
newPassword: "test12345",
|
||||
});
|
||||
expect(changePasswordResponse.status).toBe(401);
|
||||
});
|
||||
test("Should return 401 if incorrect password", async () => {
|
||||
const changePasswordResponse = await request(app)
|
||||
.patch("/user-service/change-password")
|
||||
.set("Cookie", authToken)
|
||||
.send({
|
||||
oldPassword: "test12345",
|
||||
newPassword: "test1234",
|
||||
});
|
||||
expect(changePasswordResponse.status).toBe(401);
|
||||
});
|
||||
test("Should return 400 if no old password", async () => {
|
||||
const changePasswordResponse = await request(app)
|
||||
.patch("/user-service/change-password")
|
||||
.set("Cookie", authToken)
|
||||
.send({
|
||||
newPassword: "test12345",
|
||||
});
|
||||
expect(changePasswordResponse.status).toBe(400);
|
||||
});
|
||||
test("Should return 400 if no new password", async () => {
|
||||
const changePasswordResponse = await request(app)
|
||||
.patch("/user-service/change-password")
|
||||
.set("Cookie", authToken)
|
||||
.send({
|
||||
oldPassword: "test1234",
|
||||
});
|
||||
expect(changePasswordResponse.status).toBe(400);
|
||||
});
|
||||
test("Should return 400 if old password length is less than 6", async () => {
|
||||
const changePasswordResponse = await request(app)
|
||||
.patch("/user-service/change-password")
|
||||
.set("Cookie", authToken)
|
||||
.send({
|
||||
oldPassword: "",
|
||||
newPassword: "test12345",
|
||||
});
|
||||
expect(changePasswordResponse.status).toBe(400);
|
||||
});
|
||||
test("Should return 400 if old password length is greater than 256", async () => {
|
||||
const changePasswordResponse = await request(app)
|
||||
.patch("/user-service/change-password")
|
||||
.set("Cookie", authToken)
|
||||
.send({
|
||||
oldPassword: "a".repeat(257),
|
||||
newPassword: "test12345",
|
||||
});
|
||||
expect(changePasswordResponse.status).toBe(400);
|
||||
});
|
||||
test("Should return 400 if new password length is less than 6", async () => {
|
||||
const changePasswordResponse = await request(app)
|
||||
.patch("/user-service/change-password")
|
||||
.set("Cookie", authToken)
|
||||
.send({
|
||||
oldPassword: "test1234",
|
||||
newPassword: "",
|
||||
});
|
||||
expect(changePasswordResponse.status).toBe(400);
|
||||
});
|
||||
test("Should return 400 if new password length is greater than 256", async () => {
|
||||
const changePasswordResponse = await request(app)
|
||||
.patch("/user-service/change-password")
|
||||
.set("Cookie", authToken)
|
||||
.send({
|
||||
oldPassword: "test1234",
|
||||
newPassword: "a".repeat(257),
|
||||
});
|
||||
expect(changePasswordResponse.status).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Get/Create user token: POST /user-service/get-token", () => {
|
||||
test("Should return user token", async () => {
|
||||
const getTokenResponse = await request(app)
|
||||
.post("/user-service/get-token")
|
||||
.set("Cookie", authToken)
|
||||
.send();
|
||||
|
||||
expect(getTokenResponse.status).toBe(201);
|
||||
});
|
||||
test("Should return 401 if not authorized", async () => {
|
||||
const getTokenResponse = await request(app)
|
||||
.post("/user-service/get-token")
|
||||
.set("Cookie", "access-token=test")
|
||||
.send();
|
||||
|
||||
expect(getTokenResponse.status).toBe(401);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Logout: POST /user-service/logout", () => {
|
||||
test("Should logout user", async () => {
|
||||
const userDbCheck = await mongoose.model("User").findOne({
|
||||
email: user.body.user.email,
|
||||
});
|
||||
|
||||
expect(userDbCheck.tokens.length).toBe(1);
|
||||
|
||||
const logoutResponse = await request(app)
|
||||
.post("/user-service/logout")
|
||||
.set("Cookie", authToken)
|
||||
.send();
|
||||
|
||||
const userDbCheck2 = await mongoose.model("User").findOne({
|
||||
email: user.body.user.email,
|
||||
});
|
||||
|
||||
expect(userDbCheck2.tokens.length).toBe(0);
|
||||
expect(logoutResponse.status).toBe(200);
|
||||
});
|
||||
test("Should return 401 if not authorized", async () => {
|
||||
const logoutResponse = await request(app)
|
||||
.post("/user-service/logout")
|
||||
.set("Cookie", "access-token=test")
|
||||
.send();
|
||||
|
||||
expect(logoutResponse.status).toBe(401);
|
||||
});
|
||||
});
|
||||
describe("Logout all: POST /user-service/logout-all", () => {
|
||||
test("Should logout all users", async () => {
|
||||
await request(app).post("/user-service/login").send({
|
||||
email: user.body.user.email,
|
||||
password: "test1234",
|
||||
});
|
||||
|
||||
const userDbCheck = await mongoose.model("User").findOne({
|
||||
email: user.body.user.email,
|
||||
});
|
||||
|
||||
expect(userDbCheck.tokens.length).toBe(2);
|
||||
|
||||
const logoutAllResponse = await request(app)
|
||||
.post("/user-service/logout-all")
|
||||
.set("Cookie", authToken)
|
||||
.send();
|
||||
|
||||
const userDbCheck2 = await mongoose.model("User").findOne({
|
||||
email: user.body.user.email,
|
||||
});
|
||||
|
||||
expect(userDbCheck2.tokens.length).toBe(0);
|
||||
expect(logoutAllResponse.status).toBe(200);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user