commit c49851de8b57c496a6386018d615dcc0575badbe Author: Vassiliy Yegorov Date: Mon Jan 19 18:44:24 2026 +0700 init diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..efaff15 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "mcp__acp__Write", + "mcp__acp__Bash", + "mcp__acp__Edit" + ] + } +} diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..bb352f8 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +node_modules +npm-debug.log +data +.git +.gitignore diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml new file mode 100644 index 0000000..1bcde50 --- /dev/null +++ b/.gitea/workflows/build.yaml @@ -0,0 +1,39 @@ +name: Build SMS Gateway +on: + push: + branches: [main] + +env: + REGISTRY: git.realmanual.ru + IMAGE_PREFIX: ${{ gitea.repository }} + +permissions: + contents: read + packages: write + +jobs: + build: + name: Build image + runs-on: ubuntu-22.04 + container: catthehacker/ubuntu:act-latest + steps: + - uses: actions/checkout@v3 + - name: Read Version + id: version + run: echo "VERSION=$(cat VERSION)" >> $GITHUB_OUTPUT + - name: Log in to Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.TOKEN }} + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: ./ + file: ./Dockerfile + push: true + tags: | + ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/kb-admin:${{ steps.version.outputs.VERSION }} + ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/kb-admin:latest + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..431464e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM node:20-alpine + +WORKDIR /app + +COPY package*.json ./ + +RUN npm install --production + +COPY . . + +RUN mkdir -p /app/data + +EXPOSE 3000 + +CMD ["npm", "start"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..d6a4e0a --- /dev/null +++ b/README.md @@ -0,0 +1,171 @@ +# SMS OTP Gateway + +Легковесный сервис для логирования SMS-сообщений. Принимает запросы в формате SMS-провайдера и сохраняет их в локальный файл для просмотра. + +## Требования + +- Docker и Docker Compose + +## Быстрый старт + +```bash +# Сборка и запуск +docker-compose up -d --build + +# Просмотр логов +docker-compose logs -f + +# Остановка +docker-compose down +``` + +Сервис будет доступен на `http://localhost:3000` + +## API + +### POST /send-msg + +Принимает SMS-сообщение и сохраняет в протокол. + +**Content-Type:** `application/x-www-form-urlencoded` + +**Параметры:** + +| Параметр | Тип | Описание | +|----------|--------|-------------------| +| login | string | Логин отправителя | +| psw | string | Пароль | +| phones | string | Номер телефона | +| mes | string | Текст сообщения | + +**Пример запроса:** + +```bash +curl -X POST http://localhost:3000/send-msg \ + -d "login=admin&psw=secret&phones=+79001234567&mes=Your code: 1234" +``` + +**Ответ (успех):** + +```json +{ + "status": "ok", + "id": 1705678901234 +} +``` + +**Ответ (неверные credentials):** + +```json +{ + "error": "Invalid credentials" +} +``` + +### GET /view-all-sms + +Возвращает все сохраненные сообщения. + +**Пример запроса:** + +```bash +curl http://localhost:3000/view-all-sms +``` + +**Ответ:** + +```json +{ + "messages": [ + { + "id": 1705678901234, + "timestamp": "2025-01-19T12:00:00.000Z", + "login": "user", + "phone": "+79001234567", + "message": "Your code: 1234" + } + ] +} +``` + +### POST /clear-all-sms + +Очищает все сохраненные сообщения. + +**Пример запроса:** + +```bash +curl -X POST http://localhost:3000/clear-all-sms +``` + +**Ответ:** + +```json +{ + "status": "ok", + "message": "All messages cleared" +} +``` + +## Веб-интерфейс + +Доступен по адресу `http://localhost:3000` + +Функции: +- Просмотр всех сообщений в реальном времени (автообновление каждые 5 сек) +- Ручное обновление списка +- Очистка всех сообщений + +## Хранение данных + +Сообщения сохраняются в файл `data/base.json`. Директория `data/` монтируется как volume, данные сохраняются между перезапусками контейнера. + +## Конфигурация + +| Переменная | По умолчанию | Описание | +|--------------|--------------|------------------------------| +| PORT | 3000 | Порт сервера | +| SMS_LOGIN | - | Логин для авторизации (обяз) | +| SMS_PASSWORD | - | Пароль для авторизации (обяз)| + +Переменные `SMS_LOGIN` и `SMS_PASSWORD` задаются в `docker-compose.yml`: + +```yaml +environment: + - SMS_LOGIN=admin + - SMS_PASSWORD=secret +``` + +Изменение порта в docker-compose.yml: + +```yaml +services: + sms-gateway: + ports: + - "8080:3000" # внешний:внутренний +``` + +## Структура проекта + +``` +sms-opt-gateway/ +├── server.js # Express сервер +├── package.json # Зависимости +├── Dockerfile # Docker образ +├── docker-compose.yml # Compose конфигурация +├── .dockerignore +├── public/ +│ └── index.html # Веб-интерфейс +└── data/ + └── base.json # Хранилище сообщений +``` + +## Локальная разработка + +```bash +# Установка зависимостей +npm install + +# Запуск +npm start +``` diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..6c6aa7c --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.1.0 \ No newline at end of file diff --git a/data/base.json b/data/base.json new file mode 100644 index 0000000..b379c13 --- /dev/null +++ b/data/base.json @@ -0,0 +1,3 @@ +{ + "messages": [] +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..3682afe --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,12 @@ +services: + sms-gateway: + build: . + container_name: sms-otp-gateway + ports: + - "3000:3000" + volumes: + - ./data:/app/data + environment: + - SMS_LOGIN=admin + - SMS_PASSWORD=secret + restart: unless-stopped diff --git a/package.json b/package.json new file mode 100644 index 0000000..8e8ff5a --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "name": "sms-otp-gateway", + "version": "1.0.0", + "description": "Simple SMS OTP Gateway Service", + "main": "server.js", + "scripts": { + "start": "node server.js" + }, + "dependencies": { + "express": "^4.18.2" + } +} diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..3c097e3 --- /dev/null +++ b/public/index.html @@ -0,0 +1,153 @@ + + + + + + SMS OTP Gateway + + + +
+

SMS OTP Gateway

+
+ + +
+
+
Loading...
+
+
+
+ + + + diff --git a/server.js b/server.js new file mode 100644 index 0000000..f6bcb15 --- /dev/null +++ b/server.js @@ -0,0 +1,94 @@ +const express = require("express"); +const fs = require("fs"); +const path = require("path"); + +const app = express(); +const PORT = process.env.PORT || 3000; +const BASE_FILE = path.join(__dirname, "data", "base.json"); + +// Auth credentials from environment +const AUTH_LOGIN = process.env.SMS_LOGIN; +const AUTH_PASSWORD = process.env.SMS_PASSWORD; + +// Middleware +app.use(express.urlencoded({ extended: true })); +app.use(express.json()); +app.use(express.static("public")); + +// Ensure data directory and base.json exist +function ensureBaseFile() { + const dataDir = path.dirname(BASE_FILE); + if (!fs.existsSync(dataDir)) { + fs.mkdirSync(dataDir, { recursive: true }); + } + if (!fs.existsSync(BASE_FILE)) { + fs.writeFileSync(BASE_FILE, JSON.stringify({ messages: [] }, null, 2)); + } +} + +// Read messages from base.json +function readMessages() { + ensureBaseFile(); + const data = fs.readFileSync(BASE_FILE, "utf8"); + return JSON.parse(data).messages; +} + +// Write messages to base.json +function writeMessages(messages) { + ensureBaseFile(); + fs.writeFileSync(BASE_FILE, JSON.stringify({ messages }, null, 2)); +} + +// POST /send-msg - receive and log SMS +app.post("/send-msg", (req, res) => { + const { login, psw, phones, mes } = req.body; + + if (!login || !psw || !phones || !mes) { + return res.status(400).json({ error: "Missing required parameters" }); + } + + // Validate credentials + if (login !== AUTH_LOGIN || psw !== AUTH_PASSWORD) { + return res.status(401).json({ error: "Invalid credentials" }); + } + + const message = { + id: Date.now(), + timestamp: new Date().toISOString(), + login, + phone: phones, + message: mes, + }; + + const messages = readMessages(); + messages.push(message); + writeMessages(messages); + + console.log(`[${message.timestamp}] SMS to ${phones}: ${mes}`); + + res.json({ status: "ok", id: message.id }); +}); + +// GET /view-all-sms - view all logged messages +app.get("/view-all-sms", (req, res) => { + const messages = readMessages(); + res.json({ messages }); +}); + +// POST /clear-all-sms - clear all messages +app.post("/clear-all-sms", (req, res) => { + writeMessages([]); + console.log("All messages cleared"); + res.json({ status: "ok", message: "All messages cleared" }); +}); + +// GET / - serve main page +app.get("/", (req, res) => { + res.sendFile(path.join(__dirname, "public", "index.html")); +}); + +// Initialize and start server +ensureBaseFile(); +app.listen(PORT, () => { + console.log(`SMS OTP Gateway running on port ${PORT}`); +});