backend without proxy
This commit is contained in:
parent
c8c347b342
commit
ae8cd6e1c1
|
@ -130,3 +130,4 @@ dist
|
||||||
.yarn/install-state.gz
|
.yarn/install-state.gz
|
||||||
.pnp.*
|
.pnp.*
|
||||||
|
|
||||||
|
data
|
|
@ -0,0 +1,8 @@
|
||||||
|
FROM node:22-bullseye
|
||||||
|
ARG APP_PORT
|
||||||
|
ENV APP_PORT $APP_PORT
|
||||||
|
COPY . .
|
||||||
|
RUN npm i
|
||||||
|
|
||||||
|
EXPOSE $APP_PORT
|
||||||
|
ENTRYPOINT ["node", "./src/index.js"]
|
|
@ -0,0 +1,5 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS captchas(
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
hash CHAR(32),
|
||||||
|
solution CHAR(6)
|
||||||
|
);
|
|
@ -0,0 +1,25 @@
|
||||||
|
services:
|
||||||
|
captchas:
|
||||||
|
build: .
|
||||||
|
env_file: .env
|
||||||
|
ports:
|
||||||
|
- ${APP_PORT}:${APP_PORT}
|
||||||
|
depends_on:
|
||||||
|
- database
|
||||||
|
restart: on-failure
|
||||||
|
volumes:
|
||||||
|
- ./data/temp:/temp
|
||||||
|
- ./data/uploads:/uploads
|
||||||
|
database:
|
||||||
|
image: 'postgres:15'
|
||||||
|
volumes:
|
||||||
|
- ./data/db:/var/lib/postgresql/data
|
||||||
|
env_file: .env
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: ${DBUSER}
|
||||||
|
POSTGRES_PASSWORD: ${DBPASS}
|
||||||
|
POSTGRES_DB: ${DBNAME}
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "pg_isready", "-U", "${DBUSER}"]
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"name": "captcha_agregator",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Site for aggregating captchas and give them to users to solve",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.foxarmy.org/leca/captcha_agregator"
|
||||||
|
},
|
||||||
|
"license": "GPL-3.0-or-later",
|
||||||
|
"author": "leca",
|
||||||
|
"type": "module",
|
||||||
|
"main": "src/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node src/index.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"dotenv": "^16.5.0",
|
||||||
|
"express": "^5.1.0",
|
||||||
|
"pg": "^8.15.5",
|
||||||
|
"pug": "^3.0.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"nodemon": "^3.1.10"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
html
|
||||||
|
head
|
||||||
|
title Captcha Aggregator
|
||||||
|
meta(charset="utf-8")
|
||||||
|
meta(name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no")
|
||||||
|
meta(name="description" content="")
|
||||||
|
link(href="css/index.css" rel="stylesheet")
|
||||||
|
body
|
||||||
|
div(id="tsparticles")
|
||||||
|
main(class="box")
|
||||||
|
h2 Вход
|
||||||
|
|
||||||
|
form(id="loginForm" target="hiddenFrame")
|
||||||
|
div(class="inputBox")
|
||||||
|
label(for="username") Ник
|
||||||
|
input(type="text" name="username" id="username" placeholder="ваш ник на сервере" required=true)
|
||||||
|
|
||||||
|
div(class="inputBox")
|
||||||
|
label(for="password") Пароль
|
||||||
|
input(type="password" name="password" id="password" placeholder="ваш пароль" required=true)
|
||||||
|
|
||||||
|
div
|
||||||
|
button(type="submit" name="" style="float: left;") Войти
|
||||||
|
a(class="button" href="register" style="float: left;") Регистрация
|
||||||
|
script(type="text/javascript" src="js/index.js")
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
#Databse
|
||||||
|
DBUSER=captchas
|
||||||
|
DBHOST=localhost
|
||||||
|
DBNAME=captchas
|
||||||
|
DBPORT=5432
|
||||||
|
DBPASS=GENERATE_A_STRONG_PASSWORD_HERE
|
||||||
|
|
||||||
|
#General
|
||||||
|
APP_PORT=3000
|
||||||
|
CAPTCHA_SOURCE_URL=https://example.com
|
||||||
|
DATA_DIR=/opt/captcha_aggregator
|
|
@ -0,0 +1,20 @@
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
|
||||||
|
dotenv.config({ path: ".env" });
|
||||||
|
|
||||||
|
const getBoolean = value => { return value === 'true'? true : false }
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
dbuser: process.env.DBUSER,
|
||||||
|
dbhost: process.env.DBHOST,
|
||||||
|
dbname: process.env.DBNAME,
|
||||||
|
dbport: Number.parseInt(process.env.DBPORT),
|
||||||
|
dbpass: process.env.DBPASS,
|
||||||
|
|
||||||
|
app_port: Number.parseInt(process.env.APP_PORT),
|
||||||
|
|
||||||
|
captcha_source_url: process.env.CAPTCHA_SOURCE_URL,
|
||||||
|
data_dir: process.env.DATA_DIR
|
||||||
|
}
|
||||||
|
|
||||||
|
export default config;
|
|
@ -0,0 +1,38 @@
|
||||||
|
import CaptchaService from "../services/captcha.js";
|
||||||
|
import config from '../config.js';
|
||||||
|
import fs from 'fs/promises';
|
||||||
|
|
||||||
|
class ApiController {
|
||||||
|
async new(req, res) {
|
||||||
|
const id = await CaptchaService.new();
|
||||||
|
|
||||||
|
return res.status(200).send({"id": id});
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(req, res) {
|
||||||
|
const id = req.params.id;
|
||||||
|
|
||||||
|
const hash = await CaptchaService.get(id);
|
||||||
|
if (hash == undefined) {
|
||||||
|
return res.status(404).send({"message": "captcha not found"});
|
||||||
|
}
|
||||||
|
const image = await fs.readFile(`${config.data_dir}/${hash}.jpeg`);
|
||||||
|
|
||||||
|
return res.status(200).send(image)
|
||||||
|
}
|
||||||
|
|
||||||
|
async solve (req, res) {
|
||||||
|
const id = req.params.id;
|
||||||
|
const solution = req.body["solution"];
|
||||||
|
if (solution == undefined || solution.length != 6) {
|
||||||
|
return res.status(400).send({"message": 'please, send a valid solution. Example: {"solution":"123456"}'});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!await CaptchaService.check_and_save_solution(id, solution))
|
||||||
|
return res.status(409).send({"message": "Solution is not correct"});
|
||||||
|
|
||||||
|
return res.status(200).send({"message": "Successful"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new ApiController();
|
|
@ -0,0 +1,21 @@
|
||||||
|
import pg from 'pg';
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
|
import config from './config.js';
|
||||||
|
|
||||||
|
const { Pool } = pg;
|
||||||
|
|
||||||
|
console.log("Connecting to PostgreSQL database");
|
||||||
|
|
||||||
|
const pool = new Pool({
|
||||||
|
user: config.dbuser,
|
||||||
|
host: config.dbhost,
|
||||||
|
database: config.dbname,
|
||||||
|
password: config.dbpass,
|
||||||
|
port: config.dbport
|
||||||
|
});
|
||||||
|
|
||||||
|
pool.query(fs.readFileSync('./db_schema.psql').toString());
|
||||||
|
|
||||||
|
|
||||||
|
export default pool;
|
|
@ -0,0 +1,24 @@
|
||||||
|
import express from 'express';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
import ApiRouter from './routers/api.js';
|
||||||
|
import FrontendRouter from './routers/frontend.js';
|
||||||
|
|
||||||
|
import config from './config.js';
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
app.use(express.static(path.join('./public')));
|
||||||
|
app.use(express.urlencoded({ extended: false }));
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
app.set('view engine', 'pug');
|
||||||
|
|
||||||
|
app.use('/api', ApiRouter);
|
||||||
|
app.use('/', FrontendRouter);
|
||||||
|
|
||||||
|
const server = app.listen(config.app_port, () => {
|
||||||
|
console.log("App has been started!");
|
||||||
|
});
|
||||||
|
|
||||||
|
export default server;
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { Router } from 'express';
|
||||||
|
|
||||||
|
import ApiController from '../controllers/api.js';
|
||||||
|
|
||||||
|
const ApiRouter = new Router();
|
||||||
|
|
||||||
|
ApiRouter.post('/captcha/new', ApiController.new);
|
||||||
|
ApiRouter.get('/captcha/:id', ApiController.get);
|
||||||
|
ApiRouter.patch('/captcha/:id', ApiController.solve);
|
||||||
|
|
||||||
|
export default ApiRouter;
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { Router } from 'express';
|
||||||
|
|
||||||
|
const FrontendRouter = new Router();
|
||||||
|
|
||||||
|
FrontendRouter.get('/', async (req, res) => {
|
||||||
|
return res.render("index.pug");
|
||||||
|
});
|
||||||
|
|
||||||
|
export default FrontendRouter;
|
|
@ -0,0 +1,61 @@
|
||||||
|
import db from '../db.js';
|
||||||
|
import fs from 'fs/promises';
|
||||||
|
import config from '../config.js';
|
||||||
|
import { createHash } from 'crypto';
|
||||||
|
|
||||||
|
|
||||||
|
class CaptchaService {
|
||||||
|
async new() {
|
||||||
|
try {
|
||||||
|
const captcha = await (await fetch(config.captcha_source_url)).blob();
|
||||||
|
const buffer = Buffer.from(await captcha.arrayBuffer());
|
||||||
|
const hash = createHash('md5').update(buffer).digest('hex');
|
||||||
|
await fs.writeFile(`${config.data_dir}/${hash}.jpeg`, buffer);
|
||||||
|
|
||||||
|
const id = (await db.query("INSERT INTO captchas (hash) VALUES ($1) RETURNING id", [hash])).rows[0].id;
|
||||||
|
|
||||||
|
return id
|
||||||
|
} catch(e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async check_solution(solution) {
|
||||||
|
const body = {
|
||||||
|
"TotalSum": "78278",
|
||||||
|
"FnNumber": "9960440301173139",
|
||||||
|
"ReceiptOperationType": "1",
|
||||||
|
"DocNumber": "35704",
|
||||||
|
"DocFiscalSign": "4149689833",
|
||||||
|
"Captcha": solution,
|
||||||
|
"DocDateTime": "2022-09-21T20:28:00.000Z"
|
||||||
|
}
|
||||||
|
const result = await fetch("https://check.ofd.ru/Document/FetchReceiptFromFns", {
|
||||||
|
"headers": {
|
||||||
|
"Content-Type": "application/json;charset=utf-8"
|
||||||
|
},
|
||||||
|
"body": JSON.stringify(body),
|
||||||
|
"method": "POST",
|
||||||
|
});
|
||||||
|
return result.status != 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
async get (id) {
|
||||||
|
let result = await db.query("SELECT hash FROM captchas WHERE id = $1", [id])
|
||||||
|
if (result.rows[0] == undefined) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
return result.rows[0].hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
async check_and_save_solution (id, solution) {
|
||||||
|
if (!await this.check_solution(solution)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.query("UPDATE captchas SET solution = $1 WHERE id = $2", [solution, id]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new CaptchaService();
|
Loading…
Reference in New Issue