Commit ed59c38e authored by Birahavi Kugathasan's avatar Birahavi Kugathasan

Merge branch 'master' of...

Merge branch 'master' of http://gitlab.sliit.lk/22_23-j-36/easy-quest-smart-recruitment-tool-with-ai-backend into it19980096
parents e6fda490 7b765318
data/attempts
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# next.js build output
.next
lib
node_modules
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -2,29 +2,35 @@
"name": "keystrokedynamics",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js",
"dev": "nodemon server.js",
"build": "yarn tsc",
"start": "yarn build && node lib/index.js",
"dev": "yarn build && nodemon lib/index.js",
"burnthemall": "rm -rf node_modules package-lock.json && npm i"
},
"author": "Namit Nathwani",
"license": "ISC",
"dependencies": {
"express": "^4.17.1",
"express-winston": "^4.0.3",
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"express": "^4.18.2",
"jsonwebtoken": "^9.0.0",
"lodash": "^4.17.19",
"mathjs": "^7.2.0",
"mongoose": "^5.9.25",
"simple-statistics": "^7.1.0",
"winston": "^3.3.3"
"simple-statistics": "^7.1.0"
},
"devDependencies": {
"@types/bcryptjs": "^2.4.2",
"@types/express": "^4.17.17",
"@types/jsonwebtoken": "^9.0.1",
"@types/lodash": "^4.14.191",
"@types/node": "^18.15.1",
"eslint": "^7.5.0",
"eslint-config-airbnb": "^18.2.0",
"eslint-config-airbnb-base": "^14.2.0",
"eslint-plugin-import": "^2.22.0",
"nodemon": "^2.0.4"
"nodemon": "^2.0.4",
"typescript": "^4.9.5"
}
}
const express = require("express");
const _ = require("lodash");
const { logger } = require("../utilities/loggers");
const {
processKeystrokeData,
findUser,
signUpNewUser,
updateUser,
createSignupDataFromProcessedData,
addAttemptToKeystrokeData,
computeDataTendencies,
processAttempt,
} = require("../utilities/userUtility");
const router = express.Router();
router.use(express.json({ extended: false }));
router.use(express.urlencoded({ extended: false }));
router.post("/validateKeystroke", (req, res) => {
const processedKeystrokeData = processKeystrokeData(req.body);
res.json(processedKeystrokeData);
});
router.get("/find/:username", async (req, res) => {
res.json(await findUser(req.params.username));
});
router.get("/tendencies/:username", async (req, res) => {
const user = await findUser(req.params.username);
return res.json({
db: (await findUser(req.params.username)).keystrokeData,
calc: computeDataTendencies(user.keystrokeData),
});
});
router.post("/signup", async (req, res) => {
const { passwords, keydown, keyup, username } = req.body;
const attemptCount = passwords.length;
const processedAttempts = Array(attemptCount)
.fill()
.map((v, i) =>
processKeystrokeData({ keydown: keydown[i], keyup: keyup[i] })
);
const passwordsEqual = processedAttempts.every((v) =>
_.isEqual(v.full.keys, processedAttempts[0].full.keys)
);
const userInDb = await findUser(username);
if (!passwordsEqual) {
// If the entered passwords don't match
return res
.status(403)
.json({ success: false, msg: "Passwords don't match" });
}
if (userInDb) {
// If the user already exists in the Database
return res
.status(403)
.json({ success: false, msg: "User already exists in DB" });
}
const signupData = createSignupDataFromProcessedData(
username,
passwords,
processedAttempts
);
try {
const newUserData = await signUpNewUser(signupData);
logger.info(`Signed up ${username}`);
return res.json({
success: true,
username: newUserData.username,
});
} catch (error) {
logger.error(error);
logger.debug(req.body);
return res
.status(500)
.json({ success: false, msg: "Error signing up", error });
}
});
router.post("/login", async (req, res) => {
const { password, keydown, keyup, username, controls } = req.body;
const processedAttempt = processKeystrokeData({ keydown, keyup });
const userInDb = await findUser(username);
if (!userInDb) {
// If the user does not exist in the Database
return res
.status(403)
.json({ success: false, msg: "User does not exist in DB" });
}
const credentialsValid =
password === userInDb.password &&
_.isEqual(processedAttempt.hold.keys, userInDb.keystrokeData.hold.keys) &&
_.isEqual(
processedAttempt.flight.keys,
userInDb.keystrokeData.flight.keys
) &&
_.isEqual(processedAttempt.dd.keys, userInDb.keystrokeData.dd.keys) &&
_.isEqual(processedAttempt.full.keys, userInDb.keystrokeData.full.keys);
if (!credentialsValid) {
return res.status(403).json({ success: false, msg: "Invalid Credentials" });
}
const result = processAttempt({
userKeystrokeData: userInDb.keystrokeData,
attemptKeystrokeData: processedAttempt,
controls,
});
if (result.accepted) {
const newUserData = addAttemptToKeystrokeData({
userData: userInDb,
attemptKeystrokeData: processedAttempt,
});
newUserData.__v += 1;
await updateUser({
username,
updateData: newUserData,
});
}
return res.json({
result,
db: {
hold: userInDb.keystrokeData.hold.means,
flight: userInDb.keystrokeData.flight.means,
dd: userInDb.keystrokeData.dd.means,
full: userInDb.keystrokeData.full.means,
},
filteredDb: {
hold: userInDb.keystrokeData.hold.filteredMeans,
flight: userInDb.keystrokeData.flight.filteredMeans,
dd: userInDb.keystrokeData.dd.filteredMeans,
full: userInDb.keystrokeData.full.filteredMeans,
},
attempt: {
hold: processedAttempt.hold.times,
flight: processedAttempt.flight.times,
dd: processedAttempt.dd.times,
full: processedAttempt.full.times,
},
});
});
module.exports = router;
const express = require("express");
const mongoose = require("mongoose");
// Utilities
const { logger, expressWinstonLogger } = require("./utilities/loggers.js");
// Routes
const userRoute = require("./routes/user");
// Environment constants
const API_PORT = process.env.API_PORT || 3001;
const MONGO_URL = "";
// Service Initialisation
mongoose.connect(MONGO_URL, {
useFindAndModify: false,
useNewUrlParser: true,
useUnifiedTopology: true,
});
const db = mongoose.connection;
db.on("error", logger.error.bind(logger, "connection error:"));
db.once("open", () => {
logger.info("Connected to MongoDB Instance");
});
// Express Initialisation
const app = express();
app.use(express.urlencoded({ extended: false }));
app.use(expressWinstonLogger);
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*");
res.header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept"
);
res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
if (req.method === "OPTIONS") {
return res.status(200).end();
}
return next();
});
// Routes
app.use("/user", userRoute);
app.get("/", (req, res) => {
res.json({ msg: "Default Route" });
});
app.listen(API_PORT, () => logger.info(`Listening on port ${API_PORT}`));
export const JWT_SECRET = "JWT_SECRET_SMART_RECRUITER";
export const MONGO_URL =
"mongodb+srv://smart_req_admin:CvCbwaX6A4rnJuwv@smartreq.duejzyf.mongodb.net/App?retryWrites=true&w=majority";
export const API_PORT = process.env.API_PORT || 5000;
export const DEFAULT_CONTROLS = {
standard: {
sd: 1.5,
threshold: 65,
use: true,
},
fullStandard: {
threshold: 1,
use: true,
},
};
import { Request } from "express";
import { Query } from "express-serve-static-core";
export enum USER_TYPE {
ORGANIZATION = "ORGANIZATION",
CANDIDATE = "CANDIDATE",
}
export type KeyDetails = {
key: any;
code: any;
shiftKey: any;
time: number;
};
export type KeystrokeType = {
keys: string[];
times: number[] | any;
sums?: number[];
means?: number[];
sd?: number[];
filteredMeans?: number[];
filteredSd?: number[];
covMatrix?: number[][];
};
export type KeystrokeDataType = {
hold: KeystrokeType;
flight: KeystrokeType;
dd: KeystrokeType;
full: KeystrokeType;
};
export type AddressType = {
addressLine: string;
city: string;
country: string;
};
export type AuthType = {
userType: USER_TYPE;
email: string;
userId?: string;
password: string;
keystrokeDataTimestamps: number[];
keystrokeData: KeystrokeDataType;
controls?: ControlsType;
};
export type CandidateType = {
_id?: string;
name: string;
bio: string;
contacts: {
email: string;
phone: string;
address: AddressType;
residentialAddress?: AddressType;
};
dateOfBirth: string;
jobIds: string[];
profilePicture: string;
};
export type OrganizationType = {
_id?: string;
name: string;
description: string;
contacts: {
email: string;
phone: string[];
address: AddressType;
website: string;
};
profilePicture: string;
};
export type ControlsType = {
standard: {
sd: number;
threshold: number;
use: boolean;
};
fullStandard: {
threshold: number;
use: boolean;
};
};
export interface TypedRequest<T extends Query, U> extends Request {
body: U;
query: T;
}
//PAYLOADS
export type SignUpPayload = {
passwords: string[];
keydown: KeyDetails[][];
keyup: KeyDetails[][];
email: string;
userType: USER_TYPE;
candidate?: CandidateType;
organization?: OrganizationType;
};
export type SignInPayload = {
password: string;
keydown: KeyDetails[];
keyup: KeyDetails[];
email: string;
userType: USER_TYPE;
};
export type UpdatePasswordPayload = {
passwords: string[];
keydown: KeyDetails[][];
keyup: KeyDetails[][];
oldPassword: string;
};
import express from "express";
import mongoose from "mongoose";
import cors from "cors";
import { API_PORT, MONGO_URL } from "./config/contants";
const app = express();
app.use(cors());
// Routes
const authRoute = require("./routes/auth");
// Environment constants
// Service Initialisation
mongoose.connect(MONGO_URL, {
useFindAndModify: false,
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
});
// const db = mongoose.connection;
// db.on("error", (error) => console.log(error, "connection error:"));
// db.once("open", () => {
// console.log("Connected to MongoDB Instance");
// });
// Express Initialisation
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
// Routes
app.use("/auth", authRoute);
app.listen(API_PORT, () => console.log(`Listening on port ${API_PORT}`));
import { Request, Response, NextFunction } from "express";
import * as jwt from "jsonwebtoken";
import { JWT_SECRET } from "../config/contants";
export const authMiddleware = (
req: Request,
res: Response,
next: NextFunction
) => {
const token = req.header("auth_token");
if (!token) {
return res.status(401).send("Authorization denied");
} else {
const decodedToken = <jwt.JwtPayload>jwt.verify(token, JWT_SECRET);
if (decodedToken) {
req.query.userId = decodedToken.userId;
return next();
} else {
return res.status(401).send("Unauthorized Access");
}
}
};
const mongoose = require('mongoose');
import { Schema, model } from "mongoose";
import { AuthType, USER_TYPE } from "../config/types";
const userSchema = new mongoose.Schema({
username: { type: String, unique: true },
const authSchema = new Schema<AuthType>({
userType: { type: String, require: true, default: USER_TYPE.CANDIDATE },
email: { type: String, unique: true },
userId: String,
password: String,
keystrokeDataTimestamps: [Date],
keystrokeData: {
hold: {
......@@ -47,6 +49,18 @@ const userSchema = new mongoose.Schema({
covMatrix: [[Number]],
},
},
controls: {
standard: {
sd: { type: Number, default: 1.5, require: false },
threshold: { type: Number, default: 65, require: false },
use: { type: Boolean, default: true, require: false },
},
fullStandard: {
threshold: { type: Number, default: 1, require: false },
use: { type: Boolean, default: true, require: false },
},
},
});
module.exports = mongoose.model('User', userSchema);
const Auth = model<AuthType>("Auth", authSchema);
export default Auth;
import { Schema, model } from "mongoose";
import { AddressType, CandidateType } from "../config/types";
const AddressSchema = new Schema<AddressType>(
{
addressLine: String,
city: String,
country: String,
},
{ id: false }
);
const ContactsSchema = new Schema<AddressType>(
{
email: { type: String, require: false },
phone: { type: String, require: false },
address: { type: AddressSchema, require: false },
residentialAddress: { type: AddressSchema, require: false },
},
{ id: false }
);
const candidateSchema = new Schema<CandidateType>({
name: String,
bio: String,
contacts: { type: ContactsSchema, require: false },
dateOfBirth: String,
jobIds: [{ type: Schema.Types.ObjectId, ref: "jobs" }],
profilePicture: String,
});
const Candidates = model<CandidateType>("candidates", candidateSchema);
export default Candidates;
import { Schema, model } from "mongoose";
import { AddressType, OrganizationType } from "../config/types";
const AddressSchema = new Schema<AddressType>(
{
addressLine: String,
city: String,
country: String,
},
{ id: false }
);
const ContactsSchema = new Schema<AddressType>(
{
email: String,
phone: String,
address: { type: AddressSchema },
website: String,
},
{ id: false }
);
const organizationSchema = new Schema<OrganizationType>({
name: String,
description: String,
contacts: { type: ContactsSchema, require: false },
profilePicture: String,
});
const Organizations = model<OrganizationType>(
"organizations",
organizationSchema
);
export default Organizations;
import { Router } from "express";
import * as _ from "lodash";
import * as jwt from "jsonwebtoken";
import * as bcrypt from "bcryptjs";
import {
ControlsType,
KeystrokeDataType,
SignInPayload,
SignUpPayload,
TypedRequest,
UpdatePasswordPayload,
USER_TYPE,
} from "../config/types";
import Auth from "../models/Auth";
import Candidates from "../models/Candidate";
import Organizations from "../models/Organization";
import {
processKeystrokeData,
findUser,
createSignupDataFromProcessedData,
addAttemptToKeystrokeData,
computeDataTendencies,
processAttempt,
updatePasswordFromProcessedData,
} from "../utilities/keystroke";
import { DEFAULT_CONTROLS, JWT_SECRET } from "../config/contants";
import { authMiddleware } from "../middlewares/auth";
const router = Router();
router.post("/signup", async (req: TypedRequest<{}, SignUpPayload>, res) => {
const {
passwords,
keydown,
keyup,
email,
userType,
candidate,
organization,
} = req.body;
const attemptCount = passwords.length;
const auth = await Auth.findOne({ email });
if (auth) {
return res
.status(403)
.json({ success: false, msg: "User already exists!" });
}
const processedAttempts: KeystrokeDataType[] = Array(attemptCount)
.fill(0)
.map((v, i) =>
processKeystrokeData({ keydown: keydown[i], keyup: keyup[i] })
);
const passwordsEqual = processedAttempts.every((v) =>
_.isEqual(v.full.keys, processedAttempts[0].full.keys)
);
if (!passwordsEqual) {
return res
.status(403)
.json({ success: false, msg: "Passwords don't match" });
}
try {
let newUser;
if (userType === USER_TYPE.CANDIDATE) {
newUser = await Candidates.create(candidate);
} else {
newUser = await Organizations.create(organization);
}
const signupData = await createSignupDataFromProcessedData(
email,
passwords,
userType,
newUser.id,
processedAttempts
);
await Auth.create(signupData);
return res.json({
success: true,
});
} catch (error) {
return res
.status(500)
.json({ success: false, msg: "Error signing up", error });
}
});
router.post("/login", async (req: TypedRequest<{}, SignInPayload>, res) => {
const { password, keydown, keyup, email, userType } = req.body;
const auth = await Auth.findOne({ email });
if (!auth) {
return res
.status(403)
.json({ success: false, msg: "User does not exist in DB" });
}
const processedAttempt: KeystrokeDataType = processKeystrokeData({
keydown,
keyup,
});
const isMatch = await bcrypt.compare(password, auth.password);
if (!isMatch) {
return res.status(403).json({ success: false, msg: "Invalid Credentials" });
}
const result = processAttempt({
userKeystrokeData: auth.keystrokeData,
attemptKeystrokeData: processedAttempt,
controls: auth.controls || DEFAULT_CONTROLS,
});
if (result.accepted) {
const newUserData = addAttemptToKeystrokeData({
userData: auth,
attemptKeystrokeData: processedAttempt,
});
await Auth.updateOne({ email }, { $set: newUserData });
let user;
if (userType === USER_TYPE.CANDIDATE) {
user = await Candidates.findById(auth.userId);
} else {
user = await Organizations.findById(auth.userId);
}
const token = await jwt.sign({ userId: auth.userId }, JWT_SECRET, {
expiresIn: "2h",
});
return res.json({
success: true,
user,
token,
keystrokeResult: {
result,
db: {
hold: auth.keystrokeData.hold.means,
flight: auth.keystrokeData.flight.means,
dd: auth.keystrokeData.dd.means,
full: auth.keystrokeData.full.means,
},
filteredDb: {
hold: auth.keystrokeData.hold.filteredMeans,
flight: auth.keystrokeData.flight.filteredMeans,
dd: auth.keystrokeData.dd.filteredMeans,
full: auth.keystrokeData.full.filteredMeans,
},
attempt: {
hold: processedAttempt.hold.times,
flight: processedAttempt.flight.times,
dd: processedAttempt.dd.times,
full: processedAttempt.full.times,
},
controls: auth.controls || DEFAULT_CONTROLS,
},
});
} else {
return res.status(403).json({ success: false, msg: "Invalid Credentials" });
}
});
router.get("/tendencies/:userId", authMiddleware, async (req, res) => {
findUser(req.params.userId)
.then((_auth) => {
if (_auth) {
return res.json({
db: _auth.keystrokeData,
calc: computeDataTendencies(_auth.keystrokeData),
});
} else {
throw new Error("User not found");
}
})
.catch((error) => res.status(500).send(error));
});
router.post(
"/update",
authMiddleware,
async (req: TypedRequest<{ userId: string }, ControlsType>, res) => {
try {
await Auth.updateOne(
{ userId: req.query.userId },
{ $set: { controls: req.body } }
);
return res.send("SUCCESS");
} catch (error) {
return res.status(400).send("FAILED");
}
}
);
router.post(
"/change-pwd",
authMiddleware,
async (req: TypedRequest<{ userId: string }, UpdatePasswordPayload>, res) => {
const { passwords, keydown, keyup, oldPassword } = req.body;
const attemptCount = passwords.length;
const auth = await Auth.findOne({ userId: req.query.userId });
const isMatch = await bcrypt.compare(oldPassword, auth.password);
if (!isMatch) {
return res
.status(403)
.json({ success: false, msg: "Invalid Credentials" });
}
const processedAttempts: KeystrokeDataType[] = Array(attemptCount)
.fill(0)
.map((v, i) =>
processKeystrokeData({ keydown: keydown[i], keyup: keyup[i] })
);
const passwordsEqual = processedAttempts.every((v) =>
_.isEqual(v.full.keys, processedAttempts[0].full.keys)
);
if (!passwordsEqual) {
return res
.status(403)
.json({ success: false, msg: "Passwords don't match" });
}
try {
const newPasswordData = await updatePasswordFromProcessedData(
passwords,
processedAttempts
);
Auth.updateOne({ userId: req.query.userId }, { $set: newPasswordData });
return res.json({
success: true,
});
} catch (error) {
return res
.status(500)
.json({ success: false, msg: "Error signing up", error });
}
}
);
module.exports = router;
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"lib": ["es6"],
"allowJs": true,
"outDir": "lib",
"rootDir": "src",
"strict": false,
"esModuleInterop": true,
"resolveJsonModule": true
},
"compileOnSave": true,
"include": ["src"]
}
const winston = require('winston');
const expressWinston = require('express-winston');
// const winstonLevels = {
// error: 0,
// warn: 1,
// info: 2,
// verbose: 3,
// debug: 4,
// silly: 5,
// };
module.exports.logger = winston.createLogger({
transports: [
new winston.transports.Console(),
],
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple(),
),
});
module.exports.expressWinstonLogger = expressWinston.logger({
transports: [
new winston.transports.Console({ level: 'verbose' }),
],
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple(),
),
msg() {
return '{{res.statusCode}} {{req.method}} {{req.url}} {{res.responseTime}}ms';
},
colorize: true,
meta: false,
statusLevels: false, // default value
level(req, res) {
let level = '';
if (res.statusCode >= 100) { level = 'verbose'; }
if (res.statusCode >= 400) { level = 'warn'; }
if (res.statusCode >= 500) { level = 'error'; }
// Ops is worried about hacking attempts so make Unauthorized and Forbidden critical
// if (res.statusCode == 401 || res.statusCode == 403) { level = "critical"; }
return level;
},
});
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment