Keystroke frontend

parent c0ac1c1e
Pipeline #6254 failed with stages
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Easy Quest - Smart Recruitment Tool with AI
# Easy Quest - Smart Recruitment Tool with AI
Smart recruitment tool with artificial intelligence
\ No newline at end of file
Smart recruitment tool with artificial intelligence
This diff is collapsed.
{
"name": "keystroke-auth",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.8",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.4.5",
"react-scripts": "5.0.1",
"recharts": "^2.2.0",
"typescript": "^4.9.4",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"sass": "^1.56.2"
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css"
integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65"
crossorigin="anonymous"
/>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4"
crossorigin="anonymous"
></script>
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
import React from "react";
import { Routes, BrowserRouter as Router, Route } from "react-router-dom";
import SignUp from "./components/SignUp";
import Login from "./components/Login";
import "./app.scss";
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<SignUp />} />
<Route path="/login" element={<Login />} />
</Routes>
</Router>
);
}
export default App;
body {
margin: 0;
}
p {
margin: 0;
}
import React from "react";
import { AreaChart, Tooltip, Area, ResponsiveContainer } from "recharts";
type DataType = {
real?: number[];
attempts?: number[];
title: string;
};
const Charts = ({ real, attempts, title }: DataType) => {
if (!real || !attempts) return null;
const data = real.map((_item, index) => ({
actual: _item,
attempt: attempts[index],
}));
return (
<div className="mt-5">
<h4>{title}</h4>
<ResponsiveContainer height={280} width="100%">
<AreaChart data={data}>
<Tooltip />
<Area
type="monotone"
dataKey="actual"
stroke="red"
fillOpacity={0.2}
fill="red"
/>
<Area
type="monotone"
dataKey="attempt"
stroke="blue"
fillOpacity={0.2}
fill="blue"
/>
</AreaChart>
</ResponsiveContainer>
</div>
);
};
export default Charts;
import React, { useState, useRef, useEffect } from "react";
import { initialiseControls } from "../util/controls";
import { keyEvent } from "../util/keystrokeLogger";
import { KeyDetails, Result } from "../util/types";
import Charts from "./Charts";
import ResultTable from "./ResultTable";
const controls = {
standard: {
sd: 1.5,
threshold: 65,
use: true,
},
fullStandard: {
threshold: 1,
use: true,
},
};
const Login = () => {
const [credentials, setCredentials] = useState({
userName: "",
password: "",
});
const [result, setResult] = useState<Result | null>(null);
const keyCount = useRef<number>(0);
const keydownArray = useRef<KeyDetails[]>([]);
const keyupArray = useRef<KeyDetails[]>([]);
const onChangeCredentials = (e: React.ChangeEvent<HTMLInputElement>) => {
setCredentials({ ...credentials, [e.target.name]: e.target.value });
};
useEffect(() => {
initialiseControls(controls);
}, []);
const clearData = () => {
setCredentials({ ...credentials, password: "" });
keyCount.current = 0;
keydownArray.current = [];
keyupArray.current = [];
};
const submit = () => {
const data = {
username: credentials.userName,
password: credentials.password,
keydown: keydownArray.current,
keyup: keyupArray.current,
controls,
};
clearData();
fetch("http://localhost:3001/user/login", {
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
}).then(async (res) => {
let response: Result = await res.json();
if (res.ok) {
setResult(response);
} else {
console.log(response);
}
});
};
const onKeyDown = (e: any) => {
const details = keyEvent(e);
if (!details) return;
if (
["Backspace"].includes(details.code) ||
["Backspace"].includes(details.code)
) {
return clearData();
}
keydownArray.current.push(details);
keyupArray.current.push({ ...details, time: null });
keyCount.current++;
};
const onKeyUp = (e: any) => {
const details = keyEvent(e);
if (!details) return;
if (!details.time) details.time = Date.now();
if (
["Backspace"].includes(details.code) ||
["Backspace"].includes(details.code)
) {
return clearData();
}
var reqdUpKeystroke = keyupArray.current.find(
(element) => element.code === details.code && !element.time
);
if (reqdUpKeystroke) reqdUpKeystroke.time = details.time;
if (details.code === "Enter") submit();
};
const disable = credentials.userName === "" || credentials.password === "";
return (
<div className="container">
<div className="row mt-5">
<div className="col-lg-4">
<div className="card card-body pt-4 pb-4">
<h5 className="card-title">Login</h5>
<div className="mb-3">
<label htmlFor="exampleFormControlInput1" className="form-label">
User name
</label>
<input
className="form-control"
type="text"
aria-label=".form-control-lg example"
name="userName"
onChange={onChangeCredentials}
/>
</div>
<div className="mb-3">
<label htmlFor="exampleFormControlInput1" className="form-label">
Password
</label>
<input
className="form-control"
type="password"
aria-label=".form-control-lg example"
name="password"
id="password"
onChange={onChangeCredentials}
onKeyDown={onKeyDown}
onKeyUp={onKeyUp}
/>
</div>
<button
type="button"
className="btn btn-primary"
onClick={submit}
disabled={disable}
>
Login
</button>
</div>
</div>
<div className="col-lg">
<ResultTable data={result?.result} />
</div>
</div>
<div className="row mt-5 ">
<Charts
title="Overall keystroke"
real={result?.db.full}
attempts={result?.attempt.full}
/>
</div>
<div className="row">
<Charts
title="DD keystroke"
real={result?.db.dd}
attempts={result?.attempt.dd}
/>
<Charts
title="Hold keystroke"
real={result?.db.hold}
attempts={result?.attempt.hold}
/>
<Charts
title="Flight keystroke"
real={result?.db.flight}
attempts={result?.attempt.flight}
/>
</div>
</div>
);
};
export default Login;
import React from "react";
import { Stat } from "../util/types";
const ResultTable = ({ data }: { data?: Stat | null }) => {
if (!data) return null;
return (
<table id="dataTable" className="table">
<thead>
<tr>
<th></th>
<th>Standard</th>
<th>Standard (Full)</th>
</tr>
</thead>
<tbody>
<tr>
<th className="text-right">Threshold</th>
<td>{data.standard.threshold}</td>
<td>{data.fullStandard.threshold}</td>
</tr>
<tr>
<th className="text-right">SD Multiplier</th>
<td>{data.standard.sd}</td>
<td>-</td>
</tr>
<tr>
<th className="text-right">Score/Distance</th>
<td>{(data.standard.inRangePercent.full as number).toFixed(2)}</td>
<td>
{(data.fullStandard.normedDistance.full as number).toFixed(2)}
</td>
</tr>
<tr>
<th className="text-right">Accepted</th>
<td>{data.standard.inRange.full ? "true" : "false"}</td>
<td>{data.fullStandard.inRange.full ? "true" : "false"}</td>
</tr>
</tbody>
<tfoot>
<tr>
<th className="text-right">Attempt Accepted</th>
<th className="text-center" colSpan={5}>
{data.accepted ? "Authenicated" : "Unauthorized"}
</th>
</tr>
</tfoot>
</table>
);
};
export default ResultTable;
import React, { useState, useRef, useEffect } from "react";
import { keyEvent } from "../util/keystrokeLogger";
import { KeyDetails } from "../util/types";
const SignUp = () => {
const [alert, setAlert] = useState<string | null>(null);
const [is2FAenabled, setis2FAenabled] = useState<boolean>(false);
const [credentials, setCredentials] = useState({
userName: "",
password: "",
confrimPassword: "",
twoFAPassword: "",
});
const keydownArray = useRef<KeyDetails[][]>([]);
const keyupArray = useRef<KeyDetails[][]>([]);
const keyCount = useRef<number[]>([]);
useEffect(() => {
resetData();
}, []);
const resetData = () => {
for (let i = 0; i < 3; i++) {
keydownArray.current.push([]);
keyupArray.current.push([]);
keyCount.current.push(0);
}
};
const onChange2FAconcent = () => setis2FAenabled(!is2FAenabled);
const onChangeCredentials = (e: React.ChangeEvent<HTMLInputElement>) => {
setCredentials({ ...credentials, [e.target.name]: e.target.value });
};
const onSumit = () => {
if (credentials.password !== credentials.confrimPassword) {
return setAlert("Passwords do not match");
}
resetData();
const data = {
username: credentials.userName,
passwords: [
credentials.password,
credentials.confrimPassword,
credentials.twoFAPassword,
],
keydown: keydownArray.current,
keyup: keyupArray.current,
};
fetch("http://localhost:3001/user/signup", {
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
}).then(async (res) => {
const response = await res.json();
if (response.success) {
setAlert(`${response.username} signed up successfully`);
} else {
setAlert(response.msg);
}
});
};
const clearField = (index: number) => {
if (index === 0) {
setCredentials({ ...credentials, password: "" });
} else if (index === 1) {
setCredentials({ ...credentials, confrimPassword: "" });
} else {
setCredentials({ ...credentials, twoFAPassword: "" });
}
keydownArray.current[index] = [];
keyupArray.current[index] = [];
keyCount.current[index] = 0;
};
const onKeyDown = (e: any) => {
const field = e.target.id;
const index = Number(field.substr(field.length - 1));
let details = keyEvent(e);
if (!details) return;
if (
["Backspace"].includes(details.code) ||
["Backspace"].includes(details.code)
) {
return clearField(index);
}
if (
["Tab", "NumpadEnter"].includes(details.code) ||
["Tab"].includes(details.key)
) {
details = {
...details,
code: "Enter",
key: "Enter",
};
}
keydownArray.current[index].push(details);
keyupArray.current[index].push({ ...details, time: null });
keyCount.current[index]++;
};
const onKeyUp = (e: any) => {
const field = e.target.id;
const index = Number(field.substr(field.length - 1));
let details = keyEvent(e);
if (!details) return;
if (!details.time) details.time = Date.now();
if (
["Backspace"].includes(details.code) ||
["Backspace"].includes(details.code)
) {
return clearField(index);
}
if (
["Tab", "NumpadEnter"].includes(details.code) ||
["Tab"].includes(details.key)
) {
details = {
...details,
code: "Enter",
key: "Enter",
};
}
let reqdUpKeystroke = keyupArray.current[index].find(
(element) => element.code === details?.code && !element.time
);
if (reqdUpKeystroke) reqdUpKeystroke.time = details.time;
if (index === 2) {
onSumit();
}
};
const disableSubmit =
credentials.userName === "" || credentials.password === "" || !is2FAenabled;
const renderAlert = alert && (
<div
className="alert alert-warning alert-dismissible fade show mt-3"
role="alert"
>
{alert}
<button
type="button"
className="btn-close"
data-bs-dismiss="alert"
aria-label="Close"
onClick={() => setAlert(null)}
></button>
</div>
);
return (
<div className="container">
<div className=" row">
<div className="col-lg"></div>
<div className="col-lg">
<div className="card card-body mt-5 pt-4 pb-4">
<h5 className="card-title">SignUp</h5>
<div className="mb-3">
<label htmlFor="exampleFormControlInput1" className="form-label">
User name
</label>
<input
className="form-control"
type="text"
aria-label=".form-control-lg example"
onChange={onChangeCredentials}
name="userName"
/>
</div>
<div className="mb-3">
<label htmlFor="exampleFormControlInput1" className="form-label">
Password
</label>
<input
className="form-control"
type="password"
aria-label=".form-control-lg example"
name="password"
onChange={onChangeCredentials}
onKeyDown={onKeyDown}
onKeyUp={onKeyUp}
id="password-0"
value={credentials.password}
/>
</div>
<div className="mb-3">
<label htmlFor="exampleFormControlInput1" className="form-label">
Confirm Password
</label>
<input
className="form-control"
type="password"
aria-label=".form-control-lg example"
name="confrimPassword"
onChange={onChangeCredentials}
value={credentials.confrimPassword}
onKeyDown={onKeyDown}
id="password-1"
/>
</div>
<div className="form-check mb-3">
<input
className="form-check-input"
type="checkbox"
name="2FA"
id="flexCheckDefault"
checked={is2FAenabled}
onChange={onChange2FAconcent}
/>
<label className="form-check-label" htmlFor="flexCheckDefault">
Enable keystroke dynamics 2FA
</label>
</div>
{is2FAenabled && (
<div className="mb-3">
<label
htmlFor="exampleFormControlInput1"
className="form-label"
>
Enter your password again
</label>
<input
className="form-control"
type="password"
aria-label=".form-control-lg example"
name="twoFAPassword"
onChange={onChangeCredentials}
value={credentials.twoFAPassword}
onKeyDown={onKeyDown}
id="password-2"
/>
</div>
)}
<button
type="button"
className="btn btn-primary"
disabled={disableSubmit}
onClick={onSumit}
>
Sign up
</button>
{renderAlert}
</div>
</div>
<div className="col-lg"></div>
</div>
</div>
);
};
export default SignUp;
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
/// <reference types="react-scripts" />
/* eslint-disable array-callback-return */
export function initialiseControls(controls: any) {
Object.keys(controls).map((detector) => {
if (!(detector in controls)) return null;
Object.keys(controls[detector]).map((controlType) => {
if (!(controlType in controls[detector])) return;
if (controlType === "checkbox") {
const controlId = `${detector}_${controlType}`;
const labelId = `${detector}_${controlType}_label`;
controls[detector][controlType] = document.getElementById(controlId);
controls[detector].label[controlType] =
document.getElementById(labelId);
return;
}
if (controlType === "slider") {
Object.keys(controls[detector][controlType]).map((sliderType) => {
if (!(sliderType in controls[detector][controlType])) return;
const controlId = `${detector}_${controlType}_${sliderType}`;
const labelId = `${controlId}_label`;
const slider: any = document.getElementById(controlId);
const label = document.getElementById(labelId);
if (label && slider) {
label.innerHTML = slider.value;
slider.oninput = () => {
label.innerHTML = slider.value;
};
}
controls[detector][controlType][sliderType] = slider;
controls[detector].label[sliderType] = label;
});
return;
}
});
});
}
// 16 - Shift
// 17 - Ctrl
// 18 - Alt
// 8 - Backspace
export function checkUnwantedKey(keycode: number) {
return [16, 17, 18].includes(keycode);
}
export function keyEvent(e: any) {
// Ignore keypresses for unwanted keys
if (checkUnwantedKey(e.keyCode)) {
return null;
}
// Ignore keypresses with additional modifiers
if (e.altKey || e.ctrlKey) {
return null;
}
return {
key: e.key,
code: e.code,
shiftKey: e.shiftKey,
time: Date.now(),
};
}
export type KeyDetails = {
key: any;
code: any;
shiftKey: any;
time: number | null;
};
export type Aspects = {
dd: number | boolean;
flight: number | boolean;
full: number | boolean;
hold: number | boolean;
};
export type Stat = {
standard: {
distance: Aspects;
inRange: Aspects;
inRangePercent: Aspects;
threshold: number;
sd: number;
};
filtered: {
distance: Aspects;
inRange: Aspects;
inRangePercent: Aspects;
threshold: number;
sd: number;
};
mahalanobis: {
threshold: number;
normedDistance: Aspects;
distance: Aspects;
inRange: Aspects;
};
fullStandard: {
threshold: number;
normedDistance: Aspects;
distance: Aspects;
inRange: Aspects;
};
fullFiltered: {
threshold: number;
normedDistance: Aspects;
distance: Aspects;
inRange: Aspects;
};
accepted: boolean;
};
export type KeystrokeType = {
hold: number[];
flight: number[];
dd: number[];
full: number[];
};
export type Result = {
attempt: KeystrokeType;
db: KeystrokeType;
filteredDb: KeystrokeType;
result: Stat | null;
};
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}
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