forked from fr/deguapp
		
	Added: tests, signin api, not working docker
This commit is contained in:
		
							
								
								
									
										23
									
								
								api/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								api/Dockerfile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
FROM node:18-alpine
 | 
			
		||||
 | 
			
		||||
WORKDIR /nork
 | 
			
		||||
 | 
			
		||||
COPY ../../nork .
 | 
			
		||||
 | 
			
		||||
RUN npm install
 | 
			
		||||
 | 
			
		||||
RUN npm run build
 | 
			
		||||
 | 
			
		||||
RUN npm link
 | 
			
		||||
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
 | 
			
		||||
COPY package*.json ./
 | 
			
		||||
 | 
			
		||||
RUN npm install
 | 
			
		||||
 | 
			
		||||
COPY . .
 | 
			
		||||
 | 
			
		||||
EXPOSE 3000
 | 
			
		||||
 | 
			
		||||
CMD ["npm", "start"]
 | 
			
		||||
							
								
								
									
										19
									
								
								api/docker-compose.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								api/docker-compose.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
services:
 | 
			
		||||
  mongo:
 | 
			
		||||
    image: mongo
 | 
			
		||||
    restart: always
 | 
			
		||||
    environment:
 | 
			
		||||
      MONGO_INITDB_ROOT_USERNAME: root
 | 
			
		||||
      MONGO_INITDB_ROOT_PASSWORD: root
 | 
			
		||||
    ports:
 | 
			
		||||
      - 27017:27017
 | 
			
		||||
  mongo-express:
 | 
			
		||||
    image: mongo-express
 | 
			
		||||
    restart: always
 | 
			
		||||
    ports:
 | 
			
		||||
      - 8081:8081
 | 
			
		||||
    environment:
 | 
			
		||||
      ME_CONFIG_MONGODB_ADMINUSERNAME: root
 | 
			
		||||
      ME_CONFIG_MONGODB_ADMINPASSWORD: root
 | 
			
		||||
      ME_CONFIG_MONGODB_URL: mongodb://root:root@mongo:27017/
 | 
			
		||||
      ME_CONFIG_BASICAUTH: false
 | 
			
		||||
@@ -19,22 +19,29 @@
 | 
			
		||||
	"author": "Filip Rojek",
 | 
			
		||||
	"license": "MIT",
 | 
			
		||||
	"dependencies": {
 | 
			
		||||
		"bcrypt": "^5.1.1",
 | 
			
		||||
		"colors": "1.4.0",
 | 
			
		||||
		"dotenv": "^16.4.5",
 | 
			
		||||
		"express": "^4.19.2",
 | 
			
		||||
		"fs-extra": "^10.0.0",
 | 
			
		||||
		"inquirer": "^8.1.2",
 | 
			
		||||
		"jsonwebtoken": "^9.0.2",
 | 
			
		||||
		"mongoose": "^8.3.3",
 | 
			
		||||
		"morgan": "^1.10.0",
 | 
			
		||||
		"pad": "^3.2.0",
 | 
			
		||||
		"path": "^0.12.7"
 | 
			
		||||
		"path": "^0.12.7",
 | 
			
		||||
		"yup": "^1.4.0",
 | 
			
		||||
		"yup-password": "^0.4.0"
 | 
			
		||||
	},
 | 
			
		||||
	"devDependencies": {
 | 
			
		||||
		"@biomejs/biome": "1.7.1",
 | 
			
		||||
		"@types/bcrypt": "^5.0.2",
 | 
			
		||||
		"@types/chai": "^4.2.22",
 | 
			
		||||
		"@types/express": "^4.17.21",
 | 
			
		||||
		"@types/fs-extra": "^9.0.13",
 | 
			
		||||
		"@types/inquirer": "^8.1.3",
 | 
			
		||||
		"@types/jest": "^29.5.12",
 | 
			
		||||
		"@types/jsonwebtoken": "^9.0.6",
 | 
			
		||||
		"@types/mocha": "^9.0.0",
 | 
			
		||||
		"@types/morgan": "^1.9.9",
 | 
			
		||||
		"@types/shelljs": "^0.8.11",
 | 
			
		||||
@@ -46,6 +53,7 @@
 | 
			
		||||
		"http": "^0.0.1-security",
 | 
			
		||||
		"jest": "^29.7.0",
 | 
			
		||||
		"mocha": "^9.1.3",
 | 
			
		||||
		"mongodb-memory-server": "^9.2.0",
 | 
			
		||||
		"npm-run-all": "^4.1.5",
 | 
			
		||||
		"prettier": "^2.7.1",
 | 
			
		||||
		"rimraf": "^3.0.2",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,81 @@
 | 
			
		||||
import { Request, Response } from "express";
 | 
			
		||||
import { Request, Response } from 'express';
 | 
			
		||||
import bcrypt from 'bcrypt';
 | 
			
		||||
import jwt from 'jsonwebtoken';
 | 
			
		||||
import env from '../config/environment';
 | 
			
		||||
import Docs from '../services/docsService';
 | 
			
		||||
import User from '../models/User';
 | 
			
		||||
import {Log} from 'nork'
 | 
			
		||||
import { IUser, signupExam, ISignin, signinExam } from '../validators/authValidator';
 | 
			
		||||
 | 
			
		||||
export function signup_post(req: Request, res: Response) {
 | 
			
		||||
  res.send("logged in");
 | 
			
		||||
new Docs('user', 'signup', '/api/v1/auth/signup', 'POST', 'user signup api', undefined, signupExam, 'status object');
 | 
			
		||||
export async function signup_post(req: Request, res: Response) {
 | 
			
		||||
	try {
 | 
			
		||||
		const payload: IUser = req.body;
 | 
			
		||||
 | 
			
		||||
		payload.password = await bcrypt.hash(payload.password, 12);
 | 
			
		||||
		const user = new User(payload);
 | 
			
		||||
		await user.save();
 | 
			
		||||
 | 
			
		||||
		res.status(201).json(Log.info(201, 'user was successfully signed up'));
 | 
			
		||||
	} catch (err: any) {
 | 
			
		||||
		if (err.code == 11000) {
 | 
			
		||||
			res.status(400).json(Log.error(400, 'this user already exists'));
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		Log.error(500, err);
 | 
			
		||||
		res.status(500).json(Log.error(500, 'something went wrong'));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
new Docs('user', 'signin', '/api/v1/auth/signin', 'POST', 'user signin api', undefined, signinExam, 'status object');
 | 
			
		||||
export async function signin_post(req: Request, res: Response) {
 | 
			
		||||
	try {
 | 
			
		||||
		const payload: ISignin = req.body;
 | 
			
		||||
 | 
			
		||||
		const user = await User.findOne({ email: payload.email });
 | 
			
		||||
		if (!user) {
 | 
			
		||||
			res.cookie('jwt', '', { httpOnly: true, maxAge: 0 });
 | 
			
		||||
			res.cookie('auth', false, { httpOnly: false, maxAge: 0 });
 | 
			
		||||
			res.status(401).json(Log.error(401, 'email or password is wrong'));
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (await bcrypt.compare(payload.password, user.password)) {
 | 
			
		||||
			const maxAge = 3 * 24 * 60 * 60; // 3 days in seconds
 | 
			
		||||
			const createToken = (id: any) => {
 | 
			
		||||
				return jwt.sign({ id }, env.JWT_SECRET, {
 | 
			
		||||
					expiresIn: maxAge
 | 
			
		||||
				});
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			const token = createToken(user._id);
 | 
			
		||||
			res.cookie('jwt', token, { httpOnly: true, maxAge: maxAge * 1000 });
 | 
			
		||||
			res.cookie('auth', true, { httpOnly: false, maxAge: maxAge * 1000 });
 | 
			
		||||
 | 
			
		||||
			res.json(Log.info(200, 'user is logged in'));
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		res.cookie('jwt', '', { httpOnly: true, maxAge: 0 });
 | 
			
		||||
		res.cookie('auth', false, { httpOnly: false, maxAge: 0 });
 | 
			
		||||
		res.status(401).json(Log.error(401, 'email or password is wrong'));
 | 
			
		||||
	} catch (err: any) {
 | 
			
		||||
		Log.error(500, err);
 | 
			
		||||
		res.status(500).json(Log.error(500, 'something went wrong'));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
new Docs('user', 'logout', '/api/v1/auth/logout', 'POST', 'user logout api', undefined, {}, 'status object');
 | 
			
		||||
export function logout_post(req: Request, res: Response) {
 | 
			
		||||
	res.cookie('jwt', '', { httpOnly: true, maxAge: 0 });
 | 
			
		||||
	res.cookie('auth', false, { httpOnly: false, maxAge: 0 });
 | 
			
		||||
	res.json(Log.info(200, 'user was logged out'));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
new Docs('user', 'status', '/api/v1/auth/status', 'GET', 'user login status api', undefined, undefined, 'status code | user object');
 | 
			
		||||
export function status_get(req: Request, res: Response) {
 | 
			
		||||
	let userObject = res.locals.user;
 | 
			
		||||
	userObject.password = undefined;
 | 
			
		||||
	userObject.__v = undefined;
 | 
			
		||||
	res.status(200).json(Log.info(200, 'user is logged in', userObject));
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								api/src/middlewares/validateRequest.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								api/src/middlewares/validateRequest.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
import { Request, Response, NextFunction } from 'express';
 | 
			
		||||
import { object, AnySchema } from 'yup';
 | 
			
		||||
import { Log } from 'nork';
 | 
			
		||||
 | 
			
		||||
const validate = (schema: AnySchema) => async (req: Request, res: Response, next: NextFunction) => {
 | 
			
		||||
	try {
 | 
			
		||||
		await schema.validate(req.body);
 | 
			
		||||
 | 
			
		||||
		next();
 | 
			
		||||
	} catch (err: any) {
 | 
			
		||||
		return res.status(400).json(Log.error(400, 'validation error', err));
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default validate;
 | 
			
		||||
							
								
								
									
										26
									
								
								api/src/models/User.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								api/src/models/User.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
import path from 'path';
 | 
			
		||||
import { Schema, model } from 'mongoose';
 | 
			
		||||
import { IUser } from '../validators/authValidator';
 | 
			
		||||
 | 
			
		||||
const schema = new Schema<IUser>(
 | 
			
		||||
	{
 | 
			
		||||
		username: {
 | 
			
		||||
			type: String,
 | 
			
		||||
			required: true
 | 
			
		||||
		},
 | 
			
		||||
		email: {
 | 
			
		||||
			type: String,
 | 
			
		||||
			required: true,
 | 
			
		||||
			unique: true
 | 
			
		||||
		},
 | 
			
		||||
		password: {
 | 
			
		||||
			type: String,
 | 
			
		||||
			required: true
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		timestamps: true
 | 
			
		||||
	}
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export default model(path.basename(__filename).split('.')[0], schema);
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import { Router } from "express";
 | 
			
		||||
import * as authController from "../controllers/authController";
 | 
			
		||||
//import authValidator from "../validators/authValidator";
 | 
			
		||||
import validate from '../middlewares/validateRequest'
 | 
			
		||||
import * as AuthVal from '../validators/authValidator'
 | 
			
		||||
//import handleValidation from "../middlewares/handleValidation";
 | 
			
		||||
//import { requireAuth } from "../middlewares/authMiddleware";
 | 
			
		||||
 | 
			
		||||
@@ -8,7 +9,7 @@ const router = Router();
 | 
			
		||||
 | 
			
		||||
//const mws = [requireAuth, handleValidation.handleValidationError];
 | 
			
		||||
 | 
			
		||||
router.post("/signup", authController.signup_post);
 | 
			
		||||
router.post("/auth/signup",validate(AuthVal.signup) , authController.signup_post);
 | 
			
		||||
 | 
			
		||||
//router.post(
 | 
			
		||||
//  "/login",
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
import { Request, Response, Router } from "express";
 | 
			
		||||
import path from "path";
 | 
			
		||||
import authRoutes from "./authRoutes";
 | 
			
		||||
import api_v1 from "./api_v1";
 | 
			
		||||
export const router = Router();
 | 
			
		||||
 | 
			
		||||
router.use("/api/auth", authRoutes);
 | 
			
		||||
router.use("/api/v1", api_v1);
 | 
			
		||||
 | 
			
		||||
//router.get("*", (req: Request, res: Response) => {
 | 
			
		||||
//  res.sendFile(path.join(__dirname, "../views/index.html"));
 | 
			
		||||
 
 | 
			
		||||
@@ -1,26 +1,40 @@
 | 
			
		||||
import http from "http";
 | 
			
		||||
import { app } from "./app";
 | 
			
		||||
import env from "./config/environment";
 | 
			
		||||
//const env = {
 | 
			
		||||
//  APP_PORT: 8080,
 | 
			
		||||
//  APP_HOSTNAME: "127.0.0.1",
 | 
			
		||||
//};
 | 
			
		||||
import mongoose from 'mongoose' // TODO: dopsat nork module pro db
 | 
			
		||||
import { Log } from "nork";
 | 
			
		||||
//import database from './config/database'
 | 
			
		||||
 | 
			
		||||
const port: number = env.APP_PORT || 8080;
 | 
			
		||||
const hostname: string = env.APP_HOSTNAME || "localhost";
 | 
			
		||||
 | 
			
		||||
export const server = http.createServer(app);
 | 
			
		||||
 | 
			
		||||
// Server
 | 
			
		||||
export function runServer(): void {
 | 
			
		||||
  server.listen(port, hostname, () => {
 | 
			
		||||
    Log.info(200, `Server is listening on http://${hostname}:${port}`);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//if (!env.NORK.database) {
 | 
			
		||||
runServer();
 | 
			
		||||
//export function runServer(): void {
 | 
			
		||||
//  server.listen(port, hostname, () => {
 | 
			
		||||
//    Log.info(200, `Server is listening on http://${hostname}:${port}`);
 | 
			
		||||
//  });
 | 
			
		||||
//}
 | 
			
		||||
//
 | 
			
		||||
////if (!env.NORK.database) {
 | 
			
		||||
//runServer();
 | 
			
		||||
//} else {
 | 
			
		||||
//	const db_connection = database()
 | 
			
		||||
//	runServer()
 | 
			
		||||
//}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
(async () => {
 | 
			
		||||
	if (!process.env.DOCS_GEN) {
 | 
			
		||||
		try {
 | 
			
		||||
			await mongoose.connect(env.DB_URI);
 | 
			
		||||
			Log.info(200, 'connected to db');
 | 
			
		||||
			server.listen(port, () => {
 | 
			
		||||
				Log.info(200, `Server is listening on http://localhost:${port}`);
 | 
			
		||||
			});
 | 
			
		||||
		} catch (err: any) {
 | 
			
		||||
			Log.error(500, err);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
})();
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										62
									
								
								api/src/services/docsService.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								api/src/services/docsService.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
			
		||||
import fs from 'fs';
 | 
			
		||||
import path from 'path';
 | 
			
		||||
 | 
			
		||||
export type HttpMethods = 'POST' | 'GET' | 'PUT' | 'DELETE';
 | 
			
		||||
export type ApiResponse = 'status object' | Object | string;
 | 
			
		||||
 | 
			
		||||
export class Docs {
 | 
			
		||||
	private name: string;
 | 
			
		||||
	private operation: string;
 | 
			
		||||
	private route: string;
 | 
			
		||||
	private method: HttpMethods;
 | 
			
		||||
	private parameters?: Object[];
 | 
			
		||||
	private description?: string;
 | 
			
		||||
	private body?: Object;
 | 
			
		||||
	private response?: ApiResponse;
 | 
			
		||||
 | 
			
		||||
	public constructor(name: string, operation: string, route: string, method: HttpMethods, description?: string, parameters?: Object[], body?: Object, response?: ApiResponse) {
 | 
			
		||||
		this.name = name;
 | 
			
		||||
		this.operation = operation;
 | 
			
		||||
		this.route = route;
 | 
			
		||||
		this.method = method;
 | 
			
		||||
		description ? (this.description = description) : null;
 | 
			
		||||
		parameters ? (this.parameters = parameters) : null;
 | 
			
		||||
		body ? (this.body = body) : null;
 | 
			
		||||
		response ? (this.response = response) : null;
 | 
			
		||||
 | 
			
		||||
		this.generate();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private generate(): boolean {
 | 
			
		||||
		if (!process.env.DOCS_GEN) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const jsonPath = path.join(__dirname, '../public/api.json');
 | 
			
		||||
		const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../../package.json')).toString());
 | 
			
		||||
 | 
			
		||||
		const jsonAPI = () => JSON.parse(fs.readFileSync(jsonPath).toString());
 | 
			
		||||
		const genJsonAPI = () => fs.writeFileSync(jsonPath, JSON.stringify({ version: pkg.version, endpoints: {} }));
 | 
			
		||||
 | 
			
		||||
		if (!fs.existsSync(path.join(__dirname, '../public'))) {
 | 
			
		||||
			fs.mkdirSync(path.join(__dirname, '../public'));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (!fs.existsSync(jsonPath)) {
 | 
			
		||||
			genJsonAPI();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (jsonAPI().version != pkg.version) {
 | 
			
		||||
			genJsonAPI();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const data = jsonAPI();
 | 
			
		||||
		data.endpoints[this.name] ? undefined : (data.endpoints[this.name] = {});
 | 
			
		||||
		data.endpoints[this.name][this.operation] = this;
 | 
			
		||||
		fs.writeFileSync(jsonPath, JSON.stringify(data));
 | 
			
		||||
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Docs;
 | 
			
		||||
@@ -1,48 +0,0 @@
 | 
			
		||||
import { Sequelize } from 'sequelize';
 | 
			
		||||
 | 
			
		||||
let sequelize: Sequelize | null = null;
 | 
			
		||||
 | 
			
		||||
const connectDB = async () => {
 | 
			
		||||
  sequelize = new Sequelize({
 | 
			
		||||
    dialect: 'mariadb',
 | 
			
		||||
    host: 'localhost',
 | 
			
		||||
    username: 'your_username',
 | 
			
		||||
    password: 'your_password',
 | 
			
		||||
    database: 'your_database',
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    await sequelize.authenticate();
 | 
			
		||||
    console.log('Connection to the database has been established successfully.');
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error('Unable to connect to the database:', error);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const dropDB = async () => {
 | 
			
		||||
  if (sequelize) {
 | 
			
		||||
    try {
 | 
			
		||||
      await sequelize.drop();
 | 
			
		||||
      console.log('Database dropped successfully.');
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.error('Error dropping database:', error);
 | 
			
		||||
    } finally {
 | 
			
		||||
      await sequelize.close();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const dropTables = async () => {
 | 
			
		||||
  if (sequelize) {
 | 
			
		||||
    try {
 | 
			
		||||
      await sequelize.sync({ force: true });
 | 
			
		||||
      console.log('All tables dropped successfully.');
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.error('Error dropping tables:', error);
 | 
			
		||||
    } finally {
 | 
			
		||||
      await sequelize.close();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export { connectDB, dropDB, dropTables };
 | 
			
		||||
							
								
								
									
										30
									
								
								api/src/utils/test_mongodb.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								api/src/utils/test_mongodb.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
const mongoose = require('mongoose');
 | 
			
		||||
const { MongoMemoryServer } = require('mongodb-memory-server');
 | 
			
		||||
 | 
			
		||||
let mongo: any = null;
 | 
			
		||||
 | 
			
		||||
const connectDB = async () => {
 | 
			
		||||
	mongo = await MongoMemoryServer.create({ binary: { os: { os: 'linux', dist: 'ubuntu', release: '18.04' } } }); // TODO: check that host OS is Void Linux, else remove the argument 
 | 
			
		||||
	const uri = mongo.getUri();
 | 
			
		||||
 | 
			
		||||
	await mongoose.connect(uri);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const dropDB = async () => {
 | 
			
		||||
	if (mongo) {
 | 
			
		||||
		await mongoose.connection.dropDatabase();
 | 
			
		||||
		await mongoose.connection.close();
 | 
			
		||||
		await mongo.stop();
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const dropCollections = async () => {
 | 
			
		||||
	if (mongo) {
 | 
			
		||||
		const collections = await mongoose.connection.db.collections();
 | 
			
		||||
		for (let collection of collections) {
 | 
			
		||||
			await collection.remove();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export { connectDB, dropDB, dropCollections };
 | 
			
		||||
							
								
								
									
										34
									
								
								api/src/validators/authValidator.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								api/src/validators/authValidator.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
import * as yup from 'yup';
 | 
			
		||||
import YupPassword from 'yup-password';
 | 
			
		||||
YupPassword(yup);
 | 
			
		||||
import { Schema } from 'mongoose';
 | 
			
		||||
 | 
			
		||||
interface mongooseAddition {
 | 
			
		||||
	_id?: Schema.Types.ObjectId;
 | 
			
		||||
	createdAt?: Schema.Types.Date;
 | 
			
		||||
	updatedAt?: Schema.Types.Date;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SignUp
 | 
			
		||||
export const signup = yup.object({
 | 
			
		||||
	username: yup.string().required(),
 | 
			
		||||
	email: yup.string().email().required(),
 | 
			
		||||
	password: yup.string().min(8).minLowercase(1).minUppercase(1).minNumbers(1).required()
 | 
			
		||||
});
 | 
			
		||||
export interface IUser extends yup.InferType<typeof signup>, mongooseAddition {}
 | 
			
		||||
export const signupExam: IUser = {
 | 
			
		||||
	username: 'testuser',
 | 
			
		||||
	email: 'text@example.com',
 | 
			
		||||
	password: 'Test1234'
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// SignIn
 | 
			
		||||
export const signin = yup.object({
 | 
			
		||||
	email: yup.string().email().required(),
 | 
			
		||||
	password: yup.string().min(8).minLowercase(1).minUppercase(1).minNumbers(1).required()
 | 
			
		||||
});
 | 
			
		||||
export interface ISignin extends yup.InferType<typeof signin>, mongooseAddition {}
 | 
			
		||||
export const signinExam: ISignin = {
 | 
			
		||||
	email: 'text@example.com',
 | 
			
		||||
	password: 'Test1234'
 | 
			
		||||
};
 | 
			
		||||
@@ -1,17 +1,227 @@
 | 
			
		||||
import request from "supertest"
 | 
			
		||||
import {server as app} from "../server"
 | 
			
		||||
import supertest from 'supertest';
 | 
			
		||||
import { app } from '../src/app';
 | 
			
		||||
import { connectDB, dropDB, dropCollections } from '../src/utils/test_mongodb';
 | 
			
		||||
 | 
			
		||||
describe('Auth API Endpoints', () => {
 | 
			
		||||
    afterAll((done) => {
 | 
			
		||||
        app.close(done);
 | 
			
		||||
    })
 | 
			
		||||
const request = supertest(app);
 | 
			
		||||
 | 
			
		||||
    it('should signup new user', async () => {
 | 
			
		||||
        const res = await request(app)
 | 
			
		||||
            .post('/api/auth/signup')
 | 
			
		||||
            .send({});
 | 
			
		||||
export const getJWT = async () => {
 | 
			
		||||
	try {
 | 
			
		||||
		const resReg: any = await request.post('/api/v1/auth/signup').send({
 | 
			
		||||
			email: 'test@example.local',
 | 
			
		||||
			password: 'admin1234',
 | 
			
		||||
			username: 'Test Test'
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
        expect(res.statusCode).toEqual(200)
 | 
			
		||||
        
 | 
			
		||||
    })
 | 
			
		||||
})
 | 
			
		||||
		const resLog: any = await request.post('/api/auth/login').send({
 | 
			
		||||
			email: 'test@example.local',
 | 
			
		||||
			password: 'admin1234'
 | 
			
		||||
		});
 | 
			
		||||
		if (resLog.statusCode != 200) throw 'error while logging in';
 | 
			
		||||
 | 
			
		||||
		const body = JSON.parse(resLog.text);
 | 
			
		||||
		return Promise.resolve(body.data.jwt);
 | 
			
		||||
	} catch (err: any) {
 | 
			
		||||
		console.log(err);
 | 
			
		||||
		return err;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 *
 | 
			
		||||
 * @returns JWT cookie
 | 
			
		||||
 */
 | 
			
		||||
export async function login(): Promise<string> {
 | 
			
		||||
	const res = await request.post('/api/v1/auth/signin').send({
 | 
			
		||||
		email: 'thisistest@host.local',
 | 
			
		||||
		password: 'Admin1234'
 | 
			
		||||
	});
 | 
			
		||||
	return res.headers['set-cookie'];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function signup(): Promise<boolean> {
 | 
			
		||||
	const res = await request.post('/api/v1/auth/signup').send({
 | 
			
		||||
		email: 'thisistest@host.local',
 | 
			
		||||
		password: 'Admin1234',
 | 
			
		||||
		username: 'Test Test'
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	if (res.statusCode == 201) return true;
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
describe('POST /api/v1/auth/signup', () => {
 | 
			
		||||
	describe('should drop validation error', () => {
 | 
			
		||||
		it('should drop 400 (empty request))', async () => {
 | 
			
		||||
			const res: any = await request.post('/api/v1/auth/signup').send({});
 | 
			
		||||
			expect(res.statusCode).toBe(400);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		it('should drop 400 (email))', async () => {
 | 
			
		||||
			const res: any = await request.post('/api/v1/auth/signup').send({
 | 
			
		||||
				email: '',
 | 
			
		||||
				username: 'User Admin',
 | 
			
		||||
				password: 'Admin1234'
 | 
			
		||||
			});
 | 
			
		||||
            console.log(res)
 | 
			
		||||
			const body = JSON.parse(res.text);
 | 
			
		||||
			expect(res.statusCode).toBe(400);
 | 
			
		||||
			expect(body.data.path).toBe('email');
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		it('should drop 400 (username))', async () => {
 | 
			
		||||
			const res: any = await request.post('/api/v1/auth/signup').send({
 | 
			
		||||
				email: 'admin@localhost.local',
 | 
			
		||||
				username: '',
 | 
			
		||||
				password: 'Admin1234'
 | 
			
		||||
			});
 | 
			
		||||
			const body = JSON.parse(res.text);
 | 
			
		||||
 | 
			
		||||
			expect(res.statusCode).toBe(400);
 | 
			
		||||
			expect(body.data.path).toBe('username');
 | 
			
		||||
		});
 | 
			
		||||
		it('should drop 400 (password))', async () => {
 | 
			
		||||
			const res: any = await request.post('/api/v1/auth/signup').send({
 | 
			
		||||
				email: 'admin@localhost.local',
 | 
			
		||||
				username: 'User Admin',
 | 
			
		||||
				password: ''
 | 
			
		||||
			});
 | 
			
		||||
			const body = JSON.parse(res.text);
 | 
			
		||||
 | 
			
		||||
			expect(res.statusCode).toBe(400);
 | 
			
		||||
			expect(body.data.path).toBe('password');
 | 
			
		||||
		});
 | 
			
		||||
		it('should drop 400 (password - min 8 chars', async () => {
 | 
			
		||||
			const res = await request.post('/api/v1/auth/signup').send({
 | 
			
		||||
				email: 'admin@localhost.local',
 | 
			
		||||
				username: 'User Admin',
 | 
			
		||||
				password: 'Admin12'
 | 
			
		||||
			});
 | 
			
		||||
			const body = JSON.parse(res.text);
 | 
			
		||||
 | 
			
		||||
			expect(res.statusCode).toBe(400);
 | 
			
		||||
			expect(body.data.path).toBe('password');
 | 
			
		||||
		});
 | 
			
		||||
		it('should drop 400 (password - min 1 number', async () => {
 | 
			
		||||
			const res = await request.post('/api/v1/auth/signup').send({
 | 
			
		||||
				email: 'admin@localhost.local',
 | 
			
		||||
				username: 'User Admin',
 | 
			
		||||
				password: 'Adminadmin'
 | 
			
		||||
			});
 | 
			
		||||
			const body = JSON.parse(res.text);
 | 
			
		||||
 | 
			
		||||
			expect(res.statusCode).toBe(400);
 | 
			
		||||
			expect(body.data.path).toBe('password');
 | 
			
		||||
		});
 | 
			
		||||
		it('should drop 400 (password - min 1 uppercase', async () => {
 | 
			
		||||
			const res = await request.post('/api/v1/auth/signup').send({
 | 
			
		||||
				email: 'admin@localhost.local',
 | 
			
		||||
				username: 'User Admin',
 | 
			
		||||
				password: 'admin1234'
 | 
			
		||||
			});
 | 
			
		||||
			const body = JSON.parse(res.text);
 | 
			
		||||
 | 
			
		||||
			expect(res.statusCode).toBe(400);
 | 
			
		||||
			expect(body.data.path).toBe('password');
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	test('should register an user', async () => {
 | 
			
		||||
		const res: any = await request.post('/api/v1/auth/signup').send({
 | 
			
		||||
			email: 'thisistest@host.local',
 | 
			
		||||
			password: 'Admin1234',
 | 
			
		||||
			username: 'Test Test'
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		expect(res.statusCode).toBe(201);
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
describe('POST /api/v1/auth/signin', () => {
 | 
			
		||||
	const url = '/api/v1/auth/signin';
 | 
			
		||||
 | 
			
		||||
	describe('should drop an validation error', () => {
 | 
			
		||||
		it('should drop 400 (empty)', async () => {
 | 
			
		||||
			const res = await request.post(url).send();
 | 
			
		||||
 | 
			
		||||
			expect(res.statusCode).toBe(400);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		it('should drop 400 (email)', async () => {
 | 
			
		||||
			const res = await request.post(url).send({
 | 
			
		||||
				password: 'Admin1234'
 | 
			
		||||
			});
 | 
			
		||||
			const body = JSON.parse(res.text);
 | 
			
		||||
 | 
			
		||||
			expect(res.statusCode).toBe(400);
 | 
			
		||||
			expect(body.data.path).toBe('email');
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		it('should drop 400 (password)', async () => {
 | 
			
		||||
			const res = await request.post(url).send({
 | 
			
		||||
				email: 'thisistest@host.local'
 | 
			
		||||
			});
 | 
			
		||||
			const body = JSON.parse(res.text);
 | 
			
		||||
 | 
			
		||||
			expect(res.statusCode).toBe(400);
 | 
			
		||||
			expect(body.data.path).toBe('password');
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	test('should drop 401', async () => {
 | 
			
		||||
		const res = await request.post(url).send({
 | 
			
		||||
			email: 'thisistest@host.local',
 | 
			
		||||
			password: 'Test12365465132'
 | 
			
		||||
		});
 | 
			
		||||
		expect(res.statusCode).toBe(401);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	test('should login an user', async () => {
 | 
			
		||||
		const res: any = await request.post(url).send({
 | 
			
		||||
			email: 'thisistest@host.local',
 | 
			
		||||
			password: 'Admin1234'
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		expect(res.statusCode).toBe(200);
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Throws errors idk
 | 
			
		||||
  
 | 
			
		||||
describe('POST /api/v1/auth/logout', () => {
 | 
			
		||||
	const url = '/api/v1/auth/logout';
 | 
			
		||||
	test('should drop 401 error', async () => {
 | 
			
		||||
		const res = await request.post(url).send({});
 | 
			
		||||
		expect(res.statusCode).toBe(401);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	test('should logout an user', async () => {
 | 
			
		||||
		const jwt = await login();
 | 
			
		||||
		const res = await request.post(url).set('Cookie', jwt).send({});
 | 
			
		||||
 | 
			
		||||
		res.headers['set-cookie'].forEach((el: any) => {
 | 
			
		||||
			if (el.split('=')[0] == 'jwt') {
 | 
			
		||||
				expect(Number(el.split('=')[2][0])).toBe(0);
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		expect(res.statusCode).toBe(200);
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
describe('GET /api/v1/auth/status', () => {
 | 
			
		||||
	const url = '/api/v1/auth/status';
 | 
			
		||||
	test('should return login status 401', async () => {
 | 
			
		||||
		const res = await request.get(url).send();
 | 
			
		||||
		expect(res.statusCode).toBe(401);
 | 
			
		||||
	});
 | 
			
		||||
	test('should return login status 200', async () => {
 | 
			
		||||
		const jwt = await login();
 | 
			
		||||
		const res = await request.get(url).set('Cookie', jwt).send();
 | 
			
		||||
		expect(res.statusCode).toBe(200);
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
*/
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user