Initial commit
This commit is contained in:
29
backend/.gitignore
vendored
Normal file
29
backend/.gitignore
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Hardhat files
|
||||
cache/
|
||||
artifacts/
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea/
|
||||
.vscode/
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage/
|
||||
coverage.json
|
||||
20
backend/contracts/MyContract.sol
Normal file
20
backend/contracts/MyContract.sol
Normal file
@@ -0,0 +1,20 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
contract MyContract {
|
||||
address public owner;
|
||||
|
||||
constructor() {
|
||||
owner = msg.sender;
|
||||
}
|
||||
|
||||
modifier onlyOwner() {
|
||||
require(msg.sender == owner, "Only owner can call this function");
|
||||
_;
|
||||
}
|
||||
|
||||
function setOwner(address newOwner) public onlyOwner {
|
||||
require(newOwner != address(0), "New owner cannot be zero address");
|
||||
owner = newOwner;
|
||||
}
|
||||
}
|
||||
12
backend/hardhat.config.js
Normal file
12
backend/hardhat.config.js
Normal file
@@ -0,0 +1,12 @@
|
||||
require("@nomiclabs/hardhat-waffle");
|
||||
require("dotenv").config();
|
||||
|
||||
module.exports = {
|
||||
solidity: "0.8.0",
|
||||
networks: {
|
||||
sepolia: {
|
||||
url: process.env.ETHEREUM_NETWORK_URL,
|
||||
accounts: [process.env.PRIVATE_KEY]
|
||||
}
|
||||
}
|
||||
};
|
||||
14
backend/index.js
Normal file
14
backend/index.js
Normal file
@@ -0,0 +1,14 @@
|
||||
require('dotenv').config();
|
||||
const express = require('express');
|
||||
const { ethers } = require('ethers');
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.send('Добро пожаловать в DApp-for-Business API');
|
||||
});
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Сервер запущен на http://localhost:${port}`);
|
||||
});
|
||||
33
backend/package.json
Normal file
33
backend/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "backend",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"compile": "hardhat compile",
|
||||
"deploy": "hardhat run scripts/deploy.js --network sepolia",
|
||||
"node": "hardhat node",
|
||||
"test": "hardhat test",
|
||||
"server": "node server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nomiclabs/hardhat-ethers": "^2.0.0",
|
||||
"@nomiclabs/hardhat-waffle": "^2.0.0",
|
||||
"cors": "^2.8.5",
|
||||
"ethers": "^5.0.0",
|
||||
"express": "^4.21.2",
|
||||
"express-session": "^1.18.1",
|
||||
"hardhat": "^2.9.3",
|
||||
"siwe": "^3.0.0",
|
||||
"viem": "^2.23.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express-session": "^1.18.1",
|
||||
"@types/sinon-chai": "^4.0.0",
|
||||
"chai": "4.3.7",
|
||||
"dotenv": "^16.4.7",
|
||||
"ethereum-waffle": "^4.0.10"
|
||||
}
|
||||
}
|
||||
17
backend/scripts/deploy.js
Normal file
17
backend/scripts/deploy.js
Normal file
@@ -0,0 +1,17 @@
|
||||
async function main() {
|
||||
const [deployer] = await ethers.getSigners();
|
||||
|
||||
console.log('Deploying contracts with the account:', deployer.address);
|
||||
|
||||
const MyContract = await ethers.getContractFactory('MyContract');
|
||||
const contract = await MyContract.deploy();
|
||||
|
||||
console.log('Contract deployed to:', contract.address);
|
||||
}
|
||||
|
||||
main()
|
||||
.then(() => process.exit(0))
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
211
backend/server.js
Normal file
211
backend/server.js
Normal file
@@ -0,0 +1,211 @@
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import session from 'express-session';
|
||||
import { generateNonce, SiweMessage } from 'siwe';
|
||||
import { createPublicClient, http, verifyMessage } from 'viem';
|
||||
import { sepolia } from 'viem/chains';
|
||||
|
||||
const app = express();
|
||||
|
||||
// Создаем Viem клиент для Sepolia
|
||||
const client = createPublicClient({
|
||||
chain: sepolia,
|
||||
transport: http()
|
||||
});
|
||||
|
||||
// Конфигурация CORS для работы с frontend
|
||||
app.use(cors({
|
||||
origin: ['http://localhost:5174', 'http://127.0.0.1:5173', 'http://localhost:5173'],
|
||||
credentials: true,
|
||||
methods: ['GET', 'POST'],
|
||||
allowedHeaders: ['Content-Type', 'Accept']
|
||||
}));
|
||||
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// Настройка сессий
|
||||
app.use(session({
|
||||
name: 'siwe-dapp',
|
||||
secret: "siwe-dapp-secret",
|
||||
resave: true,
|
||||
saveUninitialized: true,
|
||||
cookie: {
|
||||
secure: false,
|
||||
sameSite: 'lax',
|
||||
maxAge: 24 * 60 * 60 * 1000 // 24 часа
|
||||
}
|
||||
}));
|
||||
|
||||
// Логирование запросов
|
||||
app.use((req, res, next) => {
|
||||
console.log(`${req.method} ${req.url}`);
|
||||
next();
|
||||
});
|
||||
|
||||
// Генерация nonce
|
||||
app.get('/nonce', (_, res) => {
|
||||
try {
|
||||
const nonce = generateNonce();
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.status(200).send(nonce);
|
||||
} catch (error) {
|
||||
console.error('Ошибка генерации nonce:', error);
|
||||
res.status(500).send('Internal Server Error');
|
||||
}
|
||||
});
|
||||
|
||||
// Верификация сообщения
|
||||
app.post('/verify', async (req, res) => {
|
||||
try {
|
||||
if (!req.body.message) {
|
||||
return res.status(400).json({ error: 'SiweMessage is undefined' });
|
||||
}
|
||||
|
||||
const { message, signature } = req.body;
|
||||
console.log('Верификация сообщения:', { message, signature });
|
||||
|
||||
// Создаем и парсим SIWE сообщение
|
||||
const siweMessage = new SiweMessage(message);
|
||||
|
||||
// Проверяем базовые параметры
|
||||
if (siweMessage.chainId !== 11155111) { // Sepolia
|
||||
throw new Error('Invalid chain ID. Only Sepolia is supported.');
|
||||
}
|
||||
|
||||
if (siweMessage.domain !== '127.0.0.1:5173') {
|
||||
throw new Error('Invalid domain');
|
||||
}
|
||||
|
||||
// Проверяем время
|
||||
const currentTime = new Date().getTime();
|
||||
const messageTime = new Date(siweMessage.issuedAt).getTime();
|
||||
const timeDiff = currentTime - messageTime;
|
||||
|
||||
// Временно отключаем проверку времени для разработки
|
||||
console.log('Разница во времени:', {
|
||||
currentTime: new Date(currentTime).toISOString(),
|
||||
messageTime: new Date(messageTime).toISOString(),
|
||||
diffMinutes: Math.abs(timeDiff) / (60 * 1000)
|
||||
});
|
||||
|
||||
// Верифицируем сообщение
|
||||
console.log('Начинаем валидацию SIWE сообщения...');
|
||||
const fields = await siweMessage.validate(signature);
|
||||
console.log('SIWE валидация успешна:', fields);
|
||||
|
||||
// Проверяем подпись через viem
|
||||
console.log('Проверяем подпись через viem...');
|
||||
const isValid = await client.verifyMessage({
|
||||
address: fields.address,
|
||||
message: message,
|
||||
signature: signature
|
||||
});
|
||||
console.log('Результат проверки подписи:', isValid);
|
||||
|
||||
if (!isValid) {
|
||||
throw new Error('Invalid signature');
|
||||
}
|
||||
|
||||
console.log('Верификация успешна:', {
|
||||
address: fields.address,
|
||||
chainId: fields.chainId,
|
||||
domain: fields.domain
|
||||
});
|
||||
|
||||
// Сохраняем сессию
|
||||
req.session.siwe = {
|
||||
address: fields.address,
|
||||
chainId: fields.chainId,
|
||||
domain: fields.domain,
|
||||
issuedAt: fields.issuedAt
|
||||
};
|
||||
|
||||
req.session.save(() => {
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
address: fields.address,
|
||||
chainId: fields.chainId,
|
||||
domain: fields.domain
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка верификации:', error);
|
||||
req.session.siwe = null;
|
||||
req.session.nonce = null;
|
||||
req.session.save(() => {
|
||||
res.status(400).json({
|
||||
error: 'Verification failed',
|
||||
message: error.message
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Получение сессии
|
||||
app.get('/session', (req, res) => {
|
||||
try {
|
||||
res.json(req.session.siwe || null);
|
||||
} catch (error) {
|
||||
console.error('Ошибка получения сессии:', error);
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Выход
|
||||
app.get('/signout', (req, res) => {
|
||||
try {
|
||||
req.session.destroy((err) => {
|
||||
if (err) {
|
||||
console.error('Ошибка при удалении сессии:', err);
|
||||
return res.status(500).json({ error: 'Failed to destroy session' });
|
||||
}
|
||||
res.status(200).json({ success: true });
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка выхода:', error);
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Базовый маршрут
|
||||
app.get('/', (req, res) => {
|
||||
res.json({
|
||||
status: 'ok',
|
||||
endpoints: {
|
||||
nonce: 'GET /nonce',
|
||||
verify: 'POST /verify',
|
||||
session: 'GET /session',
|
||||
signout: 'GET /signout'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Обработка 404
|
||||
app.use((req, res) => {
|
||||
console.log(`404: ${req.method} ${req.url}`);
|
||||
res.status(404).json({
|
||||
error: 'Not Found',
|
||||
message: `Endpoint ${req.method} ${req.url} не существует`
|
||||
});
|
||||
});
|
||||
|
||||
// Обработка ошибок
|
||||
app.use((err, req, res, next) => {
|
||||
console.error('Ошибка сервера:', err);
|
||||
res.status(500).json({
|
||||
error: 'Internal Server Error',
|
||||
message: err.message
|
||||
});
|
||||
});
|
||||
|
||||
const PORT = 3000;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`SIWE сервер запущен на порту ${PORT}`);
|
||||
console.log('Доступные эндпоинты:');
|
||||
console.log(' GET / - Информация о сервере');
|
||||
console.log(' GET /nonce - Получить nonce');
|
||||
console.log(' POST /verify - Верифицировать сообщение');
|
||||
console.log(' GET /session - Получить текущую сессию');
|
||||
console.log(' GET /signout - Выйти из системы');
|
||||
});
|
||||
42
backend/test/MyContract.test.js
Normal file
42
backend/test/MyContract.test.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const { expect } = require("chai");
|
||||
const { ethers } = require("hardhat");
|
||||
|
||||
describe("MyContract", function () {
|
||||
let myContract;
|
||||
let owner;
|
||||
let addr1;
|
||||
let addr2;
|
||||
|
||||
beforeEach(async function () {
|
||||
// Получаем аккаунты из Hardhat
|
||||
[owner, addr1, addr2] = await ethers.getSigners();
|
||||
|
||||
// Деплоим контракт
|
||||
const MyContract = await ethers.getContractFactory("MyContract");
|
||||
myContract = await MyContract.deploy();
|
||||
await myContract.deployed();
|
||||
});
|
||||
|
||||
describe("Deployment", function () {
|
||||
it("Should set the right owner", async function () {
|
||||
expect(await myContract.owner()).to.equal(owner.address);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Transactions", function () {
|
||||
it("Should allow owner to set new owner", async function () {
|
||||
await myContract.setOwner(addr1.address);
|
||||
expect(await myContract.owner()).to.equal(addr1.address);
|
||||
});
|
||||
|
||||
it("Should fail if non-owner tries to set new owner", async function () {
|
||||
// Подключаемся к контракту от имени addr1
|
||||
const contractConnectedToAddr1 = myContract.connect(addr1);
|
||||
|
||||
// Ожидаем, что транзакция будет отменена
|
||||
await expect(
|
||||
contractConnectedToAddr1.setOwner(addr2.address)
|
||||
).to.be.revertedWith("Only owner can call this function");
|
||||
});
|
||||
});
|
||||
});
|
||||
4155
backend/yarn.lock
Normal file
4155
backend/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user