Sign in Sign up

parent 9f96db15
data/attempts lib
node_modules
# Logs \ No newline at end of file
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
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -4,27 +4,32 @@ ...@@ -4,27 +4,32 @@
"description": "", "description": "",
"main": "server.js", "main": "server.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "build": "yarn tsc",
"start": "node server.js", "start": "yarn build && node lib/index.js",
"dev": "nodemon server.js", "dev": "yarn build && nodemon lib/index.js",
"burnthemall": "rm -rf node_modules package-lock.json && npm i" "burnthemall": "rm -rf node_modules package-lock.json && npm i"
}, },
"author": "Namit Nathwani", "author": "Namit Nathwani",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"express": "^4.17.1", "bcryptjs": "^2.4.3",
"express-winston": "^4.0.3", "express": "^4.18.2",
"jsonwebtoken": "^9.0.0",
"lodash": "^4.17.19", "lodash": "^4.17.19",
"mathjs": "^7.2.0", "mathjs": "^7.2.0",
"mongoose": "^5.9.25", "mongoose": "^5.9.25",
"simple-statistics": "^7.1.0", "simple-statistics": "^7.1.0"
"winston": "^3.3.3"
}, },
"devDependencies": { "devDependencies": {
"@types/bcryptjs": "^2.4.2",
"@types/express": "^4.17.17",
"@types/jsonwebtoken": "^9.0.1",
"@types/lodash": "^4.14.191",
"eslint": "^7.5.0", "eslint": "^7.5.0",
"eslint-config-airbnb": "^18.2.0", "eslint-config-airbnb": "^18.2.0",
"eslint-config-airbnb-base": "^14.2.0", "eslint-config-airbnb-base": "^14.2.0",
"eslint-plugin-import": "^2.22.0", "eslint-plugin-import": "^2.22.0",
"nodemon": "^2.0.4" "nodemon": "^2.0.4",
"typescript": "^4.9.5"
} }
} }
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;
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;
};
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;
};
import express from "express";
import * as mongoose from "mongoose";
import { API_PORT, MONGO_URL } from "./config/contants";
const app = express();
// Routes
const userRoute = require("./routes/user");
// Environment constants
// Service Initialisation
mongoose.connect(MONGO_URL, {
useFindAndModify: false,
useNewUrlParser: true,
useUnifiedTopology: true,
});
// Express Initialisation
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
// Routes
app.use("/user", userRoute);
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("Authorization");
if (!token) {
return res.status(401).send("Authorization denied");
} else {
const decodedToken = <jwt.JwtPayload>jwt.verify(token, JWT_SECRET);
if (decodedToken) {
req.query.id = decodedToken.id;
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({ const authSchema = new Schema<AuthType>({
username: { type: String, unique: true }, userType: { type: String, require: true, default: USER_TYPE.CANDIDATE },
email: { type: String, unique: true },
userId: String,
password: String, password: String,
keystrokeDataTimestamps: [Date], keystrokeDataTimestamps: [Date],
keystrokeData: { keystrokeData: {
hold: { hold: {
...@@ -49,4 +51,5 @@ const userSchema = new mongoose.Schema({ ...@@ -49,4 +51,5 @@ const userSchema = new mongoose.Schema({
}, },
}); });
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;
const express = require("express"); import * as express from "express";
const _ = require("lodash"); import * as _ from "lodash";
const { logger } = require("../utilities/loggers"); import * as jwt from "jsonwebtoken";
const { import * as bcrypt from "bcryptjs";
import {
KeystrokeDataType,
SignUpPayload,
TypedRequest,
USER_TYPE,
} from "../config/types";
import Auth from "../models/Auth";
import Candidates from "../models/Candidate";
import Organizations from "../models/Organization";
import {
processKeystrokeData, processKeystrokeData,
findUser, findUser,
signUpNewUser,
updateUser,
createSignupDataFromProcessedData, createSignupDataFromProcessedData,
addAttemptToKeystrokeData, addAttemptToKeystrokeData,
computeDataTendencies, computeDataTendencies,
processAttempt, processAttempt,
} = require("../utilities/userUtility"); } from "../utilities/keystroke";
import { JWT_SECRET } from "../config/contants";
import { authMiddleware } from "../middlewares/auth";
const router = express.Router(); const router = express.Router();
router.use(express.json({ extended: false })); router.post("/signup", async (req: TypedRequest<{}, SignUpPayload>, res) => {
router.use(express.urlencoded({ extended: false })); const {
passwords,
router.post("/validateKeystroke", (req, res) => { keydown,
const processedKeystrokeData = processKeystrokeData(req.body); keyup,
res.json(processedKeystrokeData); email,
}); userType,
candidate,
router.get("/find/:username", async (req, res) => { organization,
res.json(await findUser(req.params.username)); } = req.body;
}); const attemptCount = passwords.length;
router.get("/tendencies/:username", async (req, res) => { const auth = await Auth.findOne({ email });
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) => { if (auth) {
const { passwords, keydown, keyup, username } = req.body; return res
const attemptCount = passwords.length; .status(403)
.json({ success: false, msg: "User already exists in DB" });
}
const processedAttempts = Array(attemptCount) const processedAttempts: KeystrokeDataType[] = Array(attemptCount)
.fill() .fill(0)
.map((v, i) => .map((v, i) =>
processKeystrokeData({ keydown: keydown[i], keyup: keyup[i] }) processKeystrokeData({ keydown: keydown[i], keyup: keyup[i] })
); );
...@@ -48,38 +54,34 @@ router.post("/signup", async (req, res) => { ...@@ -48,38 +54,34 @@ router.post("/signup", async (req, res) => {
_.isEqual(v.full.keys, processedAttempts[0].full.keys) _.isEqual(v.full.keys, processedAttempts[0].full.keys)
); );
const userInDb = await findUser(username);
if (!passwordsEqual) { if (!passwordsEqual) {
// If the entered passwords don't match
return res return res
.status(403) .status(403)
.json({ success: false, msg: "Passwords don't match" }); .json({ success: false, msg: "Passwords don't match" });
} }
if (userInDb) { try {
// If the user already exists in the Database let newUser;
return res if (userType === USER_TYPE.CANDIDATE) {
.status(403) newUser = await Candidates.create(candidate);
.json({ success: false, msg: "User already exists in DB" }); } else {
} newUser = await Organizations.create(organization);
}
const signupData = await createSignupDataFromProcessedData(
email,
passwords,
userType,
newUser.id,
processedAttempts
);
const signupData = createSignupDataFromProcessedData( await Auth.create(signupData);
username,
passwords,
processedAttempts
);
try {
const newUserData = await signUpNewUser(signupData);
logger.info(`Signed up ${username}`);
return res.json({ return res.json({
success: true, success: true,
username: newUserData.username,
}); });
} catch (error) { } catch (error) {
logger.error(error);
logger.debug(req.body);
return res return res
.status(500) .status(500)
.json({ success: false, msg: "Error signing up", error }); .json({ success: false, msg: "Error signing up", error });
...@@ -87,65 +89,74 @@ router.post("/signup", async (req, res) => { ...@@ -87,65 +89,74 @@ router.post("/signup", async (req, res) => {
}); });
router.post("/login", async (req, res) => { router.post("/login", async (req, res) => {
const { password, keydown, keyup, username, controls } = req.body; const { password, keydown, keyup, email, controls, userType } = req.body;
const processedAttempt = processKeystrokeData({ keydown, keyup }); const auth = await Auth.findOne({ email });
const userInDb = await findUser(username); if (!auth) {
if (!userInDb) {
// If the user does not exist in the Database
return res return res
.status(403) .status(403)
.json({ success: false, msg: "User does not exist in DB" }); .json({ success: false, msg: "User does not exist in DB" });
} }
const processedAttempt: KeystrokeDataType = processKeystrokeData({
keydown,
keyup,
});
const isMatch = await bcrypt.compare(password, auth.password);
const credentialsValid = const credentialsValid =
password === userInDb.password && isMatch &&
_.isEqual(processedAttempt.hold.keys, userInDb.keystrokeData.hold.keys) && _.isEqual(processedAttempt.hold.keys, auth.keystrokeData.hold.keys) &&
_.isEqual( _.isEqual(processedAttempt.flight.keys, auth.keystrokeData.flight.keys) &&
processedAttempt.flight.keys, _.isEqual(processedAttempt.dd.keys, auth.keystrokeData.dd.keys) &&
userInDb.keystrokeData.flight.keys _.isEqual(processedAttempt.full.keys, auth.keystrokeData.full.keys);
) &&
_.isEqual(processedAttempt.dd.keys, userInDb.keystrokeData.dd.keys) &&
_.isEqual(processedAttempt.full.keys, userInDb.keystrokeData.full.keys);
if (!credentialsValid) { if (!credentialsValid) {
return res.status(403).json({ success: false, msg: "Invalid Credentials" }); return res.status(403).json({ success: false, msg: "Invalid Credentials" });
} }
const result = processAttempt({ const result = processAttempt({
userKeystrokeData: userInDb.keystrokeData, userKeystrokeData: auth.keystrokeData,
attemptKeystrokeData: processedAttempt, attemptKeystrokeData: processedAttempt,
controls, controls,
}); });
if (result.accepted) { if (result.accepted) {
const newUserData = addAttemptToKeystrokeData({ const newUserData = addAttemptToKeystrokeData({
userData: userInDb, userData: auth,
attemptKeystrokeData: processedAttempt, attemptKeystrokeData: processedAttempt,
}); });
newUserData.__v += 1;
await updateUser({ await Auth.updateOne({ email }, { $set: newUserData });
username,
updateData: 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(auth, JWT_SECRET, { expiresIn: "2h" });
return res.json({ return res.json({
user,
result, result,
token,
db: { db: {
hold: userInDb.keystrokeData.hold.means, hold: auth.keystrokeData.hold.means,
flight: userInDb.keystrokeData.flight.means, flight: auth.keystrokeData.flight.means,
dd: userInDb.keystrokeData.dd.means, dd: auth.keystrokeData.dd.means,
full: userInDb.keystrokeData.full.means, full: auth.keystrokeData.full.means,
}, },
filteredDb: { filteredDb: {
hold: userInDb.keystrokeData.hold.filteredMeans, hold: auth.keystrokeData.hold.filteredMeans,
flight: userInDb.keystrokeData.flight.filteredMeans, flight: auth.keystrokeData.flight.filteredMeans,
dd: userInDb.keystrokeData.dd.filteredMeans, dd: auth.keystrokeData.dd.filteredMeans,
full: userInDb.keystrokeData.full.filteredMeans, full: auth.keystrokeData.full.filteredMeans,
}, },
attempt: { attempt: {
hold: processedAttempt.hold.times, hold: processedAttempt.hold.times,
...@@ -156,4 +167,24 @@ router.post("/login", async (req, res) => { ...@@ -156,4 +167,24 @@ router.post("/login", async (req, res) => {
}); });
}); });
router.post("/validateKeystroke", authMiddleware, (req, res) => {
const processedKeystrokeData = processKeystrokeData(req.body);
res.json(processedKeystrokeData);
});
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));
});
module.exports = router; module.exports = router;
{
"compilerOptions": {
"module": "commonjs",
"noImplicitReturns": true,
"noUnusedLocals": false,
"outDir": "lib",
"sourceMap": true,
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"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