Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • addRAGSources
  • chatting
  • conversations
  • editUser
  • fixTests
  • inputValidation
  • loadTests
  • main
  • rag
  • testing
  • updateEmbeddings
  • userauth
  • userhandling
13 results

Target

Select target project
  • zbhai/ragchat-api
1 result
Select Git revision
  • addRAGSources
  • chatting
  • conversations
  • editUser
  • fixTests
  • inputValidation
  • loadTests
  • main
  • rag
  • testing
  • updateEmbeddings
  • userauth
  • userhandling
13 results
Show changes

Commits on Source 2

// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`user login > given email and password are valid, but accout is unverified > should respond with a proper body 1`] = `
{
"message": "Your account is still unverified. Check your emails for the verification link.",
}
`;
exports[`user login > given the email is unknown > should respond with a proper body 1`] = `
{
"message": "Unknown combination of login credentials.",
}
`;
exports[`user login > given the inputs are valid > should respond with a proper body 1`] = `
{
"accessToken": Any<String>,
"document": {
"__v": 0,
"_id": "66a29da2942b3eb",
"createdAt": "2024-07 - 25T18: 46: 58.982Z",
"email": "user@mail.local",
"id": "66a29da2942b3ebcaf047f07",
"name": "My User",
"role": 0,
"updatedAt": "2024-07 - 25T18: 46: 58.982Z",
"username": "snoopy",
"verified": true,
},
"message": "Successfully logged in",
}
`;
exports[`user login > given the password is wrong > should respond with a proper body 1`] = `
{
"message": "Unknown combination of login credentials",
}
`;
exports[`user login > given the request body is empty > should respond with a proper body 1`] = `
{
"message": "Validation errors. Please check the error messages.",
"validationErrors": {
"email": "Required",
"password": "Required",
},
}
`;
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`refresh JWT > given authtoken is valid > should respond with a proper body 1`] = `
{
"accessToken": Any<String>,
"message": "Access token refreshed",
}
`;
exports[`refresh JWT > given refresh token is invalid > should respond with a proper body 1`] = `
{
"message": "Refresh token is invalid",
}
`;
exports[`refresh JWT > given refresh token is malformed > should respond with a proper body 1`] = `
{
"message": "jwt malformed",
}
`;
exports[`refresh JWT > given refresh token is missing > should respond with a proper body 1`] = `
{
"message": "Refresh token is missing. Consider to re-login",
}
`;
...@@ -25,17 +25,16 @@ const mockedVals = vi.hoisted(() => { ...@@ -25,17 +25,16 @@ const mockedVals = vi.hoisted(() => {
createdAt: '2024-07 - 25T18: 46: 58.982Z', createdAt: '2024-07 - 25T18: 46: 58.982Z',
updatedAt: '2024-07 - 25T18: 46: 58.982Z', updatedAt: '2024-07 - 25T18: 46: 58.982Z',
__v: 0, __v: 0,
fullname: '',
id: '66a29da2942b3ebcaf047f07' id: '66a29da2942b3ebcaf047f07'
} }
}, },
validInput: { validInput: {
email: 'john.doe@local.local', email: 'user@mail.local',
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbGIBBERISHTl9.lxQ5ZqO8qWJt15bbnSa4wrPQ02_7fvY4CgN1ZRM' token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbGIBBERISHTl9.lxQ5ZqO8qWJt15bbnSa4wrPQ02_7fvY4CgN1ZRM'
}, },
jwtPayload: { jwtPayload: {
"id": "66a29da2942b3ebcaf047f07", "id": "66a29da2942b3ebcaf047f07",
"email": "john.doe@local.local", "email": "user@mail.local",
"iat": 1722018249, "iat": 1722018249,
"exp": 1722021849 "exp": 1722021849
}, },
...@@ -58,7 +57,6 @@ vi.mock('../../utils/handleDB.js', async (importOriginal) => { ...@@ -58,7 +57,6 @@ vi.mock('../../utils/handleDB.js', async (importOriginal) => {
...await importOriginal(), ...await importOriginal(),
dbConnection: vi.fn(() => 'mocked'), dbConnection: vi.fn(() => 'mocked'),
findOneRecord: vi.fn(() => mockedVals.foundUser), findOneRecord: vi.fn(() => mockedVals.foundUser),
createRecord: vi.fn(() => mockedVals.foundUser),
updateOneRecord: vi.fn(() => mockedVals.foundUser) updateOneRecord: vi.fn(() => mockedVals.foundUser)
}; };
}); });
......
...@@ -2,137 +2,151 @@ ...@@ -2,137 +2,151 @@
import { vi, beforeAll, beforeEach, describe, expect, expectTypeOf, test, it, afterEach } from 'vitest'; import { vi, beforeAll, beforeEach, describe, expect, expectTypeOf, test, it, afterEach } from 'vitest';
import supertest from "supertest"; import supertest from "supertest";
import app from "../../app.js"; import app from "../../app.js";
// ignore expiration of the (self-signed) certificate import bcrypt from 'bcrypt';
process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0;
// set timeout
const BEFORE_ALL_TIMEOUT = 30000; // 30 sec
// set route // set route
const ROUTE = '/users/login'; const ROUTE = '/auth/login';
// prepare response of each test // prepare response of each test
let response; let response;
// ############################ // ############################
// OBJECTS // OBJECTS
// ############################ // ############################
const mockedVals = vi.hoisted(() => {
const userLoginVerifiedResponse = { return {
"record": { foundUser: {
avatar: "", _id: '66a29da2942b3eb',
collectionId: "_pb_users_auth_", username: 'snoopy',
collectionName: "users", name: 'My User',
created: "2024-05-06 07:45:18.836Z", email: 'user@mail.local',
email: "johndoe@local.local", verified: true,
emailVisibility: false, role: 0,
id: "jr9mt8yvuri3sbd", createdAt: '2024-07 - 25T18: 46: 58.982Z',
name: "John Doe", updatedAt: '2024-07 - 25T18: 46: 58.982Z',
updated: "2024-07-02 13:23:52.155Z", __v: 0,
username: "johndoe", password: 'StrongPass1!',
verified: true // password,
}, id: '66a29da2942b3ebcaf047f07'
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJfcGJfdXNlcnNfYXV0aF8iLCJleHAiOjE3MjA2NDk3NTQsImlkIjoianI5bXQ4eXZ1cmkzc2JkIiwidHlwZSI6ImF1dGhSZWNvcmQifQ.yFP1vlM_N2Fvpa_56INlaefSXnpwrm9ASCJuxPwf1Vk"
};
const userLoginUnverifiedResponse = {
"record": {
avatar: "",
collectionId: "_pb_users_auth_",
collectionName: "users",
created: "2024-05-06 07:45:18.836Z",
email: "johndoe@local.local",
emailVisibility: false,
id: "jr9mt8yvuri3sbd",
name: "John Doe",
updated: "2024-07-02 13:23:52.155Z",
username: "johndoe",
verified: false
}, },
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJfcGJfdXNlcnNfYXV0aF8iLCJleHAiOjE3MjA2NDk3NTQsImlkIjoianI5bXQ4eXZ1cmkzc2JkIiwidHlwZSI6ImF1dGhSZWNvcmQifQ.yFP1vlM_N2Fvpa_56INlaefSXnpwrm9ASCJuxPwf1Vk" validInput: {
}; email: 'user@mail.local',
password: 'StrongPass1!'
const userLoginFailedResponse = { }
code: 400,
message: 'Failed to authenticate.',
data: {}
}; };
});
// ############################ // ############################
// MOCKS // MOCKS
// ############################ // ############################
// import PocketBase Service // import Database Service
import * as pbService from '../../utils/pocketbase/handlePocketBase.js'; import * as dbService from '../../utils/handleDB.js';
// mock pbService // mock dbService
vi.mock('../../utils/pocketbase/handlePocketBase.js', async (importOriginal) => { vi.mock('../../utils/handleDB.js', async (importOriginal) => {
return { return {
...await importOriginal(), ...await importOriginal(),
pbUserLogin: vi.fn(() => userLoginVerifiedResponse) dbConnection: vi.fn(() => 'mocked'),
findOneRecord: vi.fn(() => mockedVals.foundUser),
findByIdAndUpdate: vi.fn(() => { return { ...mockedVals.foundUser, refreshToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY2MOCKED' }; })
}; };
}); });
// ############################ // ############################
// TESTS // TESTS
// ############################ // ############################
describe('user login', () => { describe('user login', async () => {
describe('given email and password are valid', () => { // prepare a hash function with current .env bcrypt settings
const _hashPw = async () => {
return await bcrypt.hash(mockedVals.foundUser.password, Number(process.env.BCRYPT_STRENGTH));
};
describe('given the inputs are valid', () => {
beforeAll(async () => { beforeAll(async () => {
// hash password
dbService.findOneRecord.mockImplementationOnce(async () => {
return { ...mockedVals.foundUser, password: await _hashPw() };
});
// set response by running route
response = await supertest(app) response = await supertest(app)
.post(ROUTE) .post(ROUTE)
.send({ .send(mockedVals.validInput);
email: 'valid.user@local.local',
password: 'ValidPassword123'
}); });
}, BEFORE_ALL_TIMEOUT);
it('should return a proper status code', () => { it('should return a proper status code', () => {
expect(response.status).toBe(200); expect(response.status).toBe(200);
}); });
it('should respond with a proper record and token', () => { it('should respond with a proper body', () => {
expect(response.body).toEqual(userLoginVerifiedResponse); expect(response.body).toMatchSnapshot({
accessToken: expect.any(String),
});
}); });
}); });
// ############################
describe('given email and password are valid, but accout is unverified', () => { describe('given email and password are valid, but accout is unverified', () => {
beforeAll(async () => { beforeAll(async () => {
pbService.pbUserLogin.mockImplementation(() => userLoginUnverifiedResponse); // hash password & unverify user
dbService.findOneRecord.mockImplementationOnce(async () => {
return { ...mockedVals.foundUser, verified: false, password: await _hashPw() };
});
// set response by running route
response = await supertest(app) response = await supertest(app)
.post(ROUTE) .post(ROUTE)
.send({ .send(mockedVals.validInput);
email: 'valid.user@local.local',
password: 'ValidPassword123'
}); });
}, BEFORE_ALL_TIMEOUT);
it('should return a proper status code', () => { it('should return a proper status code', () => {
expect(response.status).toBe(401); expect(response.status).toBe(401);
}); });
it('should respond with a proper record and token', () => { it('should respond with a proper body', () => {
expect(response.body.message).toEqual("Your account is still unverified. Check your emails for the verification link."); expect(response.body).toMatchSnapshot();
}); });
}); });
// ############################ // ############################
describe('given email or password are invalid', () => { describe('given the password is wrong', async () => {
// set response by running route
beforeAll(async () => { beforeAll(async () => {
let error = new Error('Failed to authenticate.');
error.name = 'ClientResponseError'; // hash password
error.response = userLoginFailedResponse; dbService.findOneRecord.mockImplementationOnce(async () => {
error.status = 400; return { ...mockedVals.foundUser, password: await _hashPw() };
pbService.pbUserLogin.mockImplementation(() => { throw error; }); });
const input = { ...mockedVals.validInput, password: 'invalid-password' };
response = await supertest(app) response = await supertest(app)
.post(ROUTE) .post(ROUTE)
.send({ .send(input);
email: 'invalid.user@local.local', });
password: 'invalidPassword123' it('should return a proper status code', () => {
expect(response.status).toBe(401);
});
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
}); });
}, BEFORE_ALL_TIMEOUT); });
it('should force pbUserLogin to throw an error', () => {
expect(pbService.pbUserLogin).toThrowError(); // ############################
describe('given the email is unknown', async () => {
// set response by running route
beforeAll(async () => {
dbService.findOneRecord.mockImplementationOnce(() => null);
response = await supertest(app)
.post(ROUTE)
.send(mockedVals.validInput);
}); });
it('should return a proper status code', () => { it('should return a proper status code', () => {
expect(response.status).toBe(400); expect(response.status).toBe(401);
}); });
it('should respond with a proper message', () => { it('should respond with a proper body', () => {
expect(response.body.message).toEqual('Failed to authenticate.'); expect(response.body).toMatchSnapshot();
}); });
}); });
...@@ -144,13 +158,12 @@ describe('user login', () => { ...@@ -144,13 +158,12 @@ describe('user login', () => {
response = await supertest(app) response = await supertest(app)
.post(ROUTE) .post(ROUTE)
.send(); .send();
}, BEFORE_ALL_TIMEOUT); });
it('should return a proper status code', () => { it('should return a proper status code', () => {
expect(response.status).toBe(400); expect(response.status).toBe(400);
}); });
it('should respond with a proper message', () => { it('should respond with a proper body', () => {
expect(response.body.validationErrors.email).toEqual('Required'); expect(response.body).toMatchSnapshot();
expect(response.body.validationErrors.password).toEqual('Required');
}); });
}); });
}); });
\ No newline at end of file
...@@ -2,108 +2,124 @@ ...@@ -2,108 +2,124 @@
import { vi, beforeAll, beforeEach, describe, expect, expectTypeOf, test, it, afterEach } from 'vitest'; import { vi, beforeAll, beforeEach, describe, expect, expectTypeOf, test, it, afterEach } from 'vitest';
import supertest from "supertest"; import supertest from "supertest";
import app from "../../app.js"; import app from "../../app.js";
// ignore expiration of the (self-signed) certificate import jwt from 'jsonwebtoken';
process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0;
// set timeout
const BEFORE_ALL_TIMEOUT = 30000; // 30 sec
// set route // set route
const ROUTE = '/users/refreshjwt'; const ROUTE = '/auth';
// prepare response of each test // prepare response of each test
let response; let response;
// ############################ // ############################
// OBJECTS // OBJECTS
// ############################ // ############################
const authStoreModel = { const mockedVals = vi.hoisted(() => {
avatar: "", return {
collectionId: "_pb_users_auth_", foundUser: {
collectionName: "users", _id: '66a29da2942b3eb',
created: "2024-05-06 07:45:18.836Z", username: 'snoopy',
email: "johndoe@local.local", name: 'My User',
emailVisibility: false, email: 'user@mail.local',
id: "jr9mt8yvuri3sbd", verified: true,
name: "John Doe", role: 0,
updated: "2024-07-02 13:23:52.155Z", createdAt: '2024-07 - 25T18: 46: 58.982Z',
username: "johndoe", updatedAt: '2024-07 - 25T18: 46: 58.982Z',
verified: true __v: 0,
}; password: 'StrongPass1!',
const userLoginVerifiedResponse = { // password,
"record": authStoreModel, id: '66a29da2942b3ebcaf047f07'
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJfcGJfdXNlcnNfYXV0aF8iLCJleHAiOjE3MjA2NDk3NTQsImlkIjoianI5bXQ4eXZ1cmkzc2JkIiwidHlwZSI6ImF1dGhSZWNvcmQifQ.yFP1vlM_N2Fvpa_56INlaefSXnpwrm9ASCJuxPwf1Vk" }
}; };
});
// ############################ // ############################
// MOCKS // MOCKS
// ############################ // ############################
// import PocketBase Service // import Database Service
import * as pbService from '../../utils/pocketbase/handlePocketBase.js'; import * as dbService from '../../utils/handleDB.js';
// mock pbService // mock dbService
vi.mock('../../utils/pocketbase/handlePocketBase.js', async (importOriginal) => { vi.mock('../../utils/handleDB.js', async (importOriginal) => {
return { return {
...await importOriginal(), ...await importOriginal(),
pbVerifyAccessToken: vi.fn().mockImplementation((req, res, next) => { dbConnection: vi.fn(() => 'mocked'),
next(); findOneRecord: vi.fn(() => mockedVals.foundUser),
}), findByIdAndUpdate: vi.fn(() => { return { ...mockedVals.foundUser, refreshToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY2MOCKED' }; })
pbRefreshJWT: vi.fn().mockImplementation(() => {
return userLoginVerifiedResponse;
})
}; };
}); });
// verifyRefreshToken;
// ############################ // ############################
// TESTS // TESTS
// ############################ // ############################
describe('refresh JWT', () => { describe('refresh JWT', () => {
describe('given authtoken is valid', () => { describe('given authtoken is valid', () => {
beforeAll(async () => { beforeAll(async () => {
const refreshToken = jwt.sign({ id: mockedVals.foundUser.id }, process.env.JWT_REFRESH_KEY);
// console.log('refreshToken', refreshToken);
response = await supertest(app) response = await supertest(app)
.get(ROUTE) .get(ROUTE)
.set('Authorization', 'Bearer 123valid'); .set('Cookie', `refreshToken=${refreshToken}`);
}, BEFORE_ALL_TIMEOUT); });
it('should return a proper status code', () => { it('should return a proper status code', () => {
expect(response.status).toBe(200); expect(response.status).toBe(200);
}); });
it('should respond with a proper record and token', () => { it('should respond with a proper body', () => {
expect(response.body).toEqual(userLoginVerifiedResponse); expect(response.body).toMatchSnapshot({
accessToken: expect.any(String),
});
}); });
}); });
// ############################ // ############################
describe('given authtoken is invalid', () => { describe('given refresh token is malformed', () => {
beforeAll(async () => { beforeAll(async () => {
pbService.pbVerifyAccessToken.mockImplementation((req, res, next) => { // const refreshToken = jwt.sign({ id: mockedVals.foundUser.id }, process.env.JWT_REFRESH_KEY);
res.status(403).json({ message: 'You are not logged in.' }); // console.log('refreshToken', refreshToken);
response = await supertest(app)
.get(ROUTE)
.set('Cookie', 'refreshToken=invalid');
}); });
it('should return a proper status code', () => {
expect(response.status).toBe(400);
});
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
// ############################
describe('given refresh token is invalid', () => {
beforeAll(async () => {
// const refreshToken = jwt.sign({ id: mockedVals.foundUser.id }, process.env.JWT_REFRESH_KEY);
// console.log('refreshToken', refreshToken);
response = await supertest(app) response = await supertest(app)
.get(ROUTE) .get(ROUTE)
.set('Authorization', 'Bearer 123invalid'); .set('Cookie', 'refreshToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY2YTNkYTViYTEwNjUzMmNhZTEyYTYwOSIsImlhdCI6MTcyMjA5ODM3OX0.7Pq8F2zSDwuEzlCQX3vMZAw9D43N6dSViCyVPZ_s_Zs');
}, BEFORE_ALL_TIMEOUT); });
it('should return a proper status code', () => { it('should return a proper status code', () => {
expect(response.status).toBe(403); expect(response.status).toBe(403);
}); });
it('should respond with a proper record and token', () => { it('should respond with a proper body', () => {
expect(response.body.message).toEqual('You are not logged in.'); expect(response.body).toMatchSnapshot();
}); });
}); });
// ############################ // ############################
describe('given authtoken is missing', () => { describe('given refresh token is missing', () => {
beforeAll(async () => { beforeAll(async () => {
pbService.pbVerifyAccessToken.mockImplementation((req, res, next) => {
res.status(403).json({ message: 'You are not logged in.' });
});
response = await supertest(app) response = await supertest(app)
.get(ROUTE); .get(ROUTE);
}, BEFORE_ALL_TIMEOUT); });
it('should return a proper status code', () => { it('should return a proper status code', () => {
expect(response.status).toBe(403); expect(response.status).toBe(401);
}); });
it('should respond with a proper record and token', () => { it('should respond with a proper body', () => {
expect(response.body.message).toEqual('You are not logged in.'); expect(response.body).toMatchSnapshot();
}); });
}); });
}); });
\ No newline at end of file
...@@ -23,7 +23,6 @@ const mockedVals = vi.hoisted(() => { ...@@ -23,7 +23,6 @@ const mockedVals = vi.hoisted(() => {
createdAt: '2024-07 - 25T18: 46: 58.982Z', createdAt: '2024-07 - 25T18: 46: 58.982Z',
updatedAt: '2024-07 - 25T18: 46: 58.982Z', updatedAt: '2024-07 - 25T18: 46: 58.982Z',
__v: 0, __v: 0,
fullname: '',
id: '66a29da2942b3ebcaf047f07' id: '66a29da2942b3ebcaf047f07'
} }
}, },
......
...@@ -50,7 +50,7 @@ Content-Type: application/json ...@@ -50,7 +50,7 @@ Content-Type: application/json
{ {
"email": "{{email}}", "email": "{{email}}",
"token": "yJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY2YTNkYTViYTEwNjUzMmNhZTEyYTYwOSIsImVtYWlsIjoiZW1icnVjaEB6YmgudW5pLWhhbWJ1cmcuZGUiLCJpYXQiOjE3MjIwMTgyNDksImV4cCI6MTcyMjAyMTg0OX0.X0-m9RWqCsE7gSi1ywN9zTcbtpWsWRrHGv50tVy5JmI" "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY2YTNkYTViYTEwNjUzMmNhZTEyYTYwOSIsImVtYWlsIjoiZW1icnVjaEB6YmgudW5pLWhhbWJ1cmcuZGUiLCJpYXQiOjE3MjIwODUyMTAsImV4cCI6MTcyMjA4ODgxMH0.YaNozo8sdCHcdWn5qDqgZdMjtGPJFqazSVZCZOsXAMc"
} }
......
...@@ -29,9 +29,9 @@ const mockedVals = vi.hoisted(() => { ...@@ -29,9 +29,9 @@ const mockedVals = vi.hoisted(() => {
} }
}, },
validInput: { validInput: {
name: 'John Doe', name: 'My User',
username: 'johndoe', username: 'snoopy',
email: 'john.doe@local.local', email: 'user@mail.local',
password: 'StrongPass1!', password: 'StrongPass1!',
confirmPassword: 'StrongPass1!' confirmPassword: 'StrongPass1!'
} }
......
...@@ -36,7 +36,7 @@ export const confirmVerification = async (req, res, next) => { ...@@ -36,7 +36,7 @@ export const confirmVerification = async (req, res, next) => {
req.document.verified = true; req.document.verified = true;
const updatedUser = await updateOneRecord(req.document); const updatedUser = await updateOneRecord(req.document);
// remember document but remove confidential info // remember document but remove confidential info
const document = hideConfidentialFields(User, updatedUser._doc); const document = hideConfidentialFields(User, updatedUser);
return res.json({ message: 'Account successfully verified. You can now login.' }); return res.json({ message: 'Account successfully verified. You can now login.' });
} catch (error) { } catch (error) {
next(error); next(error);
...@@ -56,12 +56,17 @@ export const login = async (req, res, next) => { ...@@ -56,12 +56,17 @@ export const login = async (req, res, next) => {
try { try {
// search for matching document // search for matching document
foundUser = await findOneRecord(User, { email: req.body.email }, '+password'); foundUser = await findOneRecord(User, { email: req.body.email }, '+password');
// console.log("🚀 ~ login ~ passwords:", req.body.password, foundUser.password);
// wrong login name // wrong login name
if (!foundUser) { if (!foundUser) {
return res.status(401).json({ message: 'Unknown combination of login credentials.' }); return res.status(401).json({ message: 'Unknown combination of login credentials.' });
} }
// wrong login name // unverified account
if (!foundUser.verified) { if (!foundUser.verified) {
return res.status(401).json({ message: 'Your account is still unverified. Check your emails for the verification link.' }); return res.status(401).json({ message: 'Your account is still unverified. Check your emails for the verification link.' });
} }
...@@ -69,11 +74,12 @@ export const login = async (req, res, next) => { ...@@ -69,11 +74,12 @@ export const login = async (req, res, next) => {
// check for correct password // check for correct password
if (await bcrypt.compare(req.body.password, foundUser.password)) { if (await bcrypt.compare(req.body.password, foundUser.password)) {
// remember document but remove confidential info // remember document but remove confidential info
const user = hideConfidentialFields(User, foundUser._doc); // res.json({ message: foundUser._doc });
const user = hideConfidentialFields(User, foundUser);
// create jsonwebtoken // create jsonwebtoken
const accessToken = createAccessToken({ id: user._id, role: user.role }); const accessToken = createAccessToken({ id: user._id, role: user.role });
const refreshToken = await createRefreshToken({ id: user._id }); const refreshToken = await createRefreshToken({ id: user._id });
if (refreshToken == null) return res.status(500).json({ message: 'Error creating refresh token' }); if (refreshToken == null) return res.status(500).json({ message: 'Error creating refresh token' });
// success // success
...@@ -85,6 +91,7 @@ export const login = async (req, res, next) => { ...@@ -85,6 +91,7 @@ export const login = async (req, res, next) => {
return res.status(401).json({ message: 'Unknown combination of login credentials' }); return res.status(401).json({ message: 'Unknown combination of login credentials' });
} }
} catch (error) { } catch (error) {
console.error('login error: ', error);
next(error); next(error);
} }
}; };
...@@ -97,8 +104,9 @@ export const login = async (req, res, next) => { ...@@ -97,8 +104,9 @@ export const login = async (req, res, next) => {
* return new accessToken and refreshToken * return new accessToken and refreshToken
*/ */
export const renewAccessToken = async (req, res, next) => { export const renewAccessToken = async (req, res, next) => {
try {
// get token from cookie
const refreshToken = req.cookies.refreshToken; const refreshToken = req.cookies.refreshToken;
if (!refreshToken) return res.status(401).json({ message: 'Refresh token is missing. Consider to re-login' }); if (!refreshToken) return res.status(401).json({ message: 'Refresh token is missing. Consider to re-login' });
// verify token // verify token
const user = await verifyRefreshToken(refreshToken); const user = await verifyRefreshToken(refreshToken);
...@@ -108,6 +116,9 @@ export const renewAccessToken = async (req, res, next) => { ...@@ -108,6 +116,9 @@ export const renewAccessToken = async (req, res, next) => {
// create & return // create & return
const accessToken = createAccessToken({ id: user._id, role: user.role }); const accessToken = createAccessToken({ id: user._id, role: user.role });
return res.json({ message: 'Access token refreshed', accessToken }); return res.json({ message: 'Access token refreshed', accessToken });
} catch (error) {
next(error);
}
}; };
......
...@@ -10,7 +10,7 @@ export const createUser = async (req, res, next) => { ...@@ -10,7 +10,7 @@ export const createUser = async (req, res, next) => {
// create user object // create user object
const newRecord = await createRecord(User, prefillDocumentObject(User, req.body)); const newRecord = await createRecord(User, prefillDocumentObject(User, req.body));
// remember document but remove confidential info // remember document but remove confidential info
req.document = hideConfidentialFields(User, newRecord._doc); req.document = hideConfidentialFields(User, newRecord);
next(); next();
// on error // on error
} catch (error) { } catch (error) {
......
...@@ -68,9 +68,9 @@ const UserSchema = new Schema( ...@@ -68,9 +68,9 @@ const UserSchema = new Schema(
// ################################# VIRTUALS // ################################# VIRTUALS
// fullName // fullName
UserSchema.virtual('fullname').get(function () { // UserSchema.virtual('fullname').get(function () {
return `${this.title || ''} ${this.firstname || ''} ${this.lastname || ''}`.replace(/\s+/g, ' ').trim(); // return `${this.title || ''} ${this.firstname || ''} ${this.lastname || ''}`.replace(/\s+/g, ' ').trim();
}); // });
// ################################# MIDDLEWARES // ################################# MIDDLEWARES
// middleware pre|post on validate|save|remove|updateOne|deleteOne // middleware pre|post on validate|save|remove|updateOne|deleteOne
......
...@@ -19,6 +19,7 @@ const generateErrorStatusCode = (error) => { ...@@ -19,6 +19,7 @@ const generateErrorStatusCode = (error) => {
switch (error.name) { switch (error.name) {
// VALIDATION ERROR // VALIDATION ERROR
case "ValidationError": case "ValidationError":
case "JsonWebTokenError":
// ZOD VALIDATION ERROR // ZOD VALIDATION ERROR
case "zodError": { case "zodError": {
return 400; return 400;
......
...@@ -93,12 +93,16 @@ export const createRefreshToken = async (payload) => { ...@@ -93,12 +93,16 @@ export const createRefreshToken = async (payload) => {
* @return {any} user from DB if exists, false if not * @return {any} user from DB if exists, false if not
*/ */
export const verifyRefreshToken = async (refreshToken) => { export const verifyRefreshToken = async (refreshToken) => {
try {
// check if in DB // check if in DB
const foundUser = await User.findOne({ refreshToken }); const foundUser = await findOneRecord(User, { refreshToken });
if (!foundUser) return false; if (!foundUser) return false;
// check if valid // check if valid
const user = jwt.verify(refreshToken, process.env.JWT_REFRESH_KEY); const user = jwt.verify(refreshToken, process.env.JWT_REFRESH_KEY);
return (user && user.id === foundUser.id ? foundUser : false); return (user && user.id === foundUser.id ? foundUser : false);
} catch (error) {
throw error;
}
}; };
/** /**
......