Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 35 additions & 52 deletions Backend/TestSpace/Register.test.js
Original file line number Diff line number Diff line change
@@ -1,91 +1,74 @@
const request = require('supertest');
const express = require('express');
const cookieParser = require('cookie-parser');
const fs = require('fs');
const path = require('path');
import request from 'supertest';
import express from 'express';
import cookieParser from 'cookie-parser';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { jest } from '@jest/globals';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Register.js の存在確認
const registerPath = path.resolve(__dirname, '../Workspace/Routes/Register.js');
const registerExists = fs.existsSync(registerPath);

// describe を動的切り替え
const describeIf = registerExists ? describe : describe.skip;

// Router を条件付きで読み込み
let registerRouter;
if (registerExists) {
registerRouter = require('../Workspace/Routes/Register');
}
jest.unstable_mockModule('../Workspace/Tools/DBPerf.js', () => ({
default: jest.fn()
}));

// DBPerfモック化
jest.mock('../Workspace/Tools/DBPerf', () => jest.fn());
const DBPerf = require('../Workspace/Tools/DBPerf');
jest.unstable_mockModule('../Workspace/Tools/AESControl.js', () => ({
encrypt: jest.fn(() => 'encrypted-key'),
decrypt: jest.fn()
}));

// Symbol SDKをモック化
jest.mock('symbol-sdk', () => {
const original = jest.requireActual('symbol-sdk');
jest.unstable_mockModule('symbol-sdk', async () => {
const original = await jest.requireActual('symbol-sdk');
return {
...original,
PrivateKey: { random: () => 'dummy-private-key' },
Account: { createFromPrivateKey: () => ({ address: { plain: () => 'dummy-address' } }) },
NetworkType: { TEST_NET: 'TEST_NET' },
facade: { SymbolFacade: jest.fn() },
PrivateKey: {
random: () => ({ toString: () => 'dummy-private-key' })
},
facade: {
SymbolFacade: jest.fn(() => ({
createAccount: () => ({ address: { toString: () => 'dummy-address' } })
}))
}
};
});

// AESControlをモック化
jest.mock('../Workspace/Tools/AESControl', () => ({
encrypt: jest.fn(() => 'encrypted-key')
}));
const { default: DBPerf } = await import('../Workspace/Tools/DBPerf.js');

let registerRouter;
if (registerExists) {
const registerModule = await import('../Workspace/Routes/Register.js');
registerRouter = registerModule.default;
}

const app = express();
app.use(express.json());
app.use(cookieParser());

if (registerExists) {
if (registerExists && registerRouter) {
app.use('/Register', registerRouter);
}

describeIf('/Register', () => {
// cookieがある場合
it('should redirect to Home if cookie exists', async () => {
const res = await request(app)
.get('/Register')
.set('Cookie', ['LoginToken=dummy-jwt']);

expect(res.status).toBe(302); // リダイレクト
expect(res.headers.location).toBe('/Home');
});

// cookieがない場合
it('should render register page if no cookie', async () => {
const res = await request(app)
.get('/Register');

expect(res.status).toBe(200);
expect(res.text).toContain('index.html'); // 登録画面が返る
});
});

describeIf('/Register/Submit', () => {
beforeEach(() => {
DBPerf.mockReset();
});

// 空送信
it('should return 400 if userId or password missing', async () => {
const res = await request(app).post('/Register/Submit').send({});
expect(res.status).toBe(400);
});

// 重複検知
it('should return 409 if userId exists', async () => {
DBPerf.mockResolvedValue([{ UserID: 'test' }]);
const res = await request(app).post('/Register/Submit').send({ userId: 'test', password: 'pass' });
expect(res.status).toBe(409);
});

// 登録成功処理
it('should succeed with new user', async () => {
DBPerf.mockResolvedValue([]);
const res = await request(app).post('/Register/Submit').send({ userId: 'newuser', password: 'pass' });
Expand Down
67 changes: 51 additions & 16 deletions Backend/Workspace/Routes/CreateRoom.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
const express = require('express');
import express from 'express';
const router = express.Router();
const jwt = require("jsonwebtoken");
const cookieParser = require("cookie-parser");
const dotenv = require("dotenv");
const path = require("path");
const multer = require("multer");
const fs = require("fs");
const VCM = require('../Tools/VerifyCookieMiddleware');
const DBPerf = require('../Tools/DBPerf');
import jwt from 'jsonwebtoken';
import cookieParser from 'cookie-parser';
import dotenv from 'dotenv';
import path from 'path';
import multer from 'multer';
import fs from 'fs';
import { fileURLToPath } from 'url';
import VCM from '../Tools/VerifyCookieMiddleware.js';
import DBPerf from '../Tools/DBPerf.js';
import CreateMosaic from '../Tools/CreateMosaicTx.js';
import { decrypt } from '../Tools/AESControl.js';

// DB から取得した encryptedPrivateKey を復号
const decryptedPrivateKey = decrypt(passwordWithPepper, encryptedPrivateKey);

// mosaic 作成時に渡す
const { mosaicId, mosaicDefinitionTx } = CreateMosaicTx({
senderPrivateKey: decryptedPrivateKey,
networkType: 'testnet'
});


Comment on lines +15 to +24
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variables passwordWithPepper, encryptedPrivateKey, and CreateMosaicTx are referenced at the module level but are not defined. These lines appear to be code that should be inside the route handler function where the necessary data is available.

Suggested change
// DB から取得した encryptedPrivateKey を復号
const decryptedPrivateKey = decrypt(passwordWithPepper, encryptedPrivateKey);
// mosaic 作成時に渡す
const { mosaicId, mosaicDefinitionTx } = CreateMosaicTx({
senderPrivateKey: decryptedPrivateKey,
networkType: 'testnet'
});

Copilot uses AI. Check for mistakes.
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// cookieを使う
router.use(cookieParser());
Expand Down Expand Up @@ -42,30 +58,49 @@ function createFileName(originalName) {


// ===ルーム作成API===
router.post("/", VCM('LoginToken', process.env.LOGIN_SECRET), upload.fields([{ name: "RoomIcon", maxCount: 1 },{ name: "TokenIcon", maxCount: 1 }]), async (req, res) => {
router.post("/", VCM('LOGIN_TOKEN', process.env.LOGIN_SECRET), upload.fields([{ name: "RoomIcon", maxCount: 1 },{ name: "MosaicIcon", maxCount: 1 }]), async (req, res) => {
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cookie name changed from 'LoginToken' to 'LOGIN_TOKEN', but this appears inconsistent with other routes which still use 'LoginToken'. This will break authentication if the cookie is actually named 'LoginToken'.

Suggested change
router.post("/", VCM('LOGIN_TOKEN', process.env.LOGIN_SECRET), upload.fields([{ name: "RoomIcon", maxCount: 1 },{ name: "MosaicIcon", maxCount: 1 }]), async (req, res) => {
router.post("/", VCM('LoginToken', process.env.LOGIN_SECRET), upload.fields([{ name: "RoomIcon", maxCount: 1 },{ name: "MosaicIcon", maxCount: 1 }]), async (req, res) => {

Copilot uses AI. Check for mistakes.
try {
const userID = req.auth.userId;
if (!userID) {
return res.status(401).json({ message: "Unauthorized" });
}

const { RoomName, MosaicName } = req.body;

if (!RoomName ||!MosaicName||!req.files?.RoomIcon ||!req.files?.MosaicIcon) {
return res.status(400).json({ message: "Bad Request" });
}

//受け取ったルームIDをMosaic名としてブロックチェーンに登録する処理をここに追加しても良い
//受け取ったMosaic情報をブロックチェーンに登録(発行)する処理をここに追加しても良い
//秘密鍵
const senderPrivateKey = await DBPerf("秘密鍵の抽出", "SELECT PrivateKey FROM Identify WHERE UserID = ?", [userID]);


const RoomIconPath = await saveIcon(req.files.RoomIcon[0], "rooms");
const MosaicIconPath = await saveIcon(req.files.TokenIcon[0], "tokens");
const MosaicIconPath = await saveIcon(req.files.MosaicIcon[0], "mosaics");

await DBPerf(
"INSERT Rooms",
"INSERT INTO Rooms(UserID, RoomName, isAdmin) VALUES (?, ?, ?)",[userID, RoomName, 1]
"INSERT INTO Rooms(UserID, RoomName) VALUES (?, ?)",[userID, RoomName]
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 'isAdmin' column was removed from the INSERT statement. If this column exists in the database schema and has no default value, this query will fail.

Suggested change
"INSERT INTO Rooms(UserID, RoomName) VALUES (?, ?)",[userID, RoomName]
"INSERT INTO Rooms(UserID, RoomName, isAdmin) VALUES (?, ?, ?)", [userID, RoomName, 1]

Copilot uses AI. Check for mistakes.
);

await DBPerf(
"INSERT RoomsDetail",
"INSERT INTO RoomsDetail (RoomName, RoomIconPath, MosaicName, MosaicIconPath) VALUES (?, ?, ?, ?)",
[RoomName, RoomIconPath, MosaicName, MosaicIconPath]
"INSERT INTO RoomsDetails (RoomName, RoomIconPath, MosaicName) VALUES (?, ?, ?)",
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The table name changed from 'RoomsDetail' to 'RoomsDetails'. Ensure this matches the actual database schema, as inconsistent table names will cause database errors.

Suggested change
"INSERT INTO RoomsDetails (RoomName, RoomIconPath, MosaicName) VALUES (?, ?, ?)",
"INSERT INTO RoomsDetail (RoomName, RoomIconPath, MosaicName) VALUES (?, ?, ?)",

Copilot uses AI. Check for mistakes.
[RoomName, RoomIconPath, MosaicName]
);

const { mosaicId: MosaicID } = await CreateMosaic({
networkType: 'testnet',
senderPrivateKey: senderPrivateKey[0].PrivateKey,
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The private key is used directly from the database without decryption. Based on the Register.js code, private keys are stored encrypted and should be decrypted using the decrypt function with passwordWithPepper before use.

Copilot uses AI. Check for mistakes.
transferable: false,
deadlineHours: 2
});

await DBPerf(
"INSERT Mosaics",
"INSERT INTO Mosaics (MosaicName, MosaicID, MosaicIconPath) VALUES (?, ?, ?)",
[MosaicName, MosaicID, MosaicIconPath]
);

res.status(201).json({ message: "Room created successfully" });
Expand All @@ -76,4 +111,4 @@ router.post("/", VCM('LoginToken', process.env.LOGIN_SECRET), upload.fields([{ n
}
);

module.exports = router;
export default router;
20 changes: 12 additions & 8 deletions Backend/Workspace/Routes/Login.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
const express = require('express');
const path = require('path');
const dotenv = require('dotenv');
import express from 'express';
import path from 'path';
import dotenv from 'dotenv';
const router = express.Router();
const argon2 = require('argon2');
const DBPerf = require('../Tools/DBPerf');
const CreateCookie = require('../Tools/CreateCookie');
import argon2 from 'argon2';
import { fileURLToPath } from 'url';
import DBPerf from '../Tools/DBPerf.js';
import CreateCookie from '../Tools/CreateCookie.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// use系
dotenv.config({ path: path.join(__dirname, "..", ".env") });
router.use(express.json());

// ========== ブロックチェーンの準備 ==========
//const symbolSdk = require('symbol-sdk');
const InverseVCM = require('../Tools/InverseVCM');
import InverseVCM from '../Tools/InverseVCM.js';
//const facade = new symbolSdk.facade.SymbolFacade('testnet');

// ========== 画面表示 ==========
Expand Down Expand Up @@ -64,4 +68,4 @@ router.post("/Submit", async (req, res) => {
}
})

module.exports = router;
export default router;
13 changes: 7 additions & 6 deletions Backend/Workspace/Routes/NFC.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
const express = require('express');
const path = require('path');
const dotenv = require('dotenv').config();
const DBPref = require('../Tools/DBPerf');
const argon2 = require('argon2');
import express from 'express';
import dotenv from 'dotenv';
import DBPref from '../Tools/DBPerf.js';
import argon2 from 'argon2';

const router = express.Router();

dotenv.config();

let latestcardUid = null; // 最新のカードUIDを保存する変数
let latestcardTime = null; // 最新のカード検知時間を保存する変数

Expand Down Expand Up @@ -71,5 +72,5 @@ router.post('/NFC', (req, res) => {
res.status(200).send({ message: 'Success' });
});

module.exports = router;
export default router;

95 changes: 95 additions & 0 deletions Backend/Workspace/Routes/Register.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Register.js
import express from 'express';
import dotenv from 'dotenv';
import cookieParser from 'cookie-parser';
import argon2 from 'argon2';
import DBPerf from '../Tools/DBPerf.js';
import { encrypt } from '../Tools/AESControl.js';
import { PrivateKey } from 'symbol-sdk';
import { SymbolFacade } from 'symbol-sdk/symbol';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';

const router = express.Router();

// ES Module で __dirname を使えるようにする
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// 環境変数読み込み
dotenv.config({ path: join(__dirname, '..', '.env') });

// use系
router.use(cookieParser());
router.use(express.json());

// ========== 画面表示 ==========
// /Register/へのアクセスでRegister画面表示
router.get('/', (req, res) => {
console.log("/Register-API is running");
res.sendFile(join(__dirname, "..", "..", "..", "Frontend", "dist", "index.html"));
});

// ========== 情報送信 ==========
// /Register/SubmitへのアクセスでRegister情報を登録
router.post('/Submit', async (req, res) => {
console.log("Submit-API is running");
try {
// 情報の取得
const { userId, password } = req.body;

if (!userId || !password) {
return res.status(400).json({ message: "Bad Request: UserIDかPasswordが不足しています。" });
}

// ユーザーIDが被っていないか確認
const exist = await DBPerf(
"Duplicate Check For UserID",
"SELECT * FROM Identify WHERE UserID = ?",
[userId]
);
if (exist.length > 0) {
return res.status(409).json({ message: "Conflict: このユーザーIDはすでに使われています" });
}

// ========== 秘密鍵保存 ==========
const privateKeyObject = PrivateKey.random();
const privateKey = privateKeyObject.toString();
const facade = new SymbolFacade('testnet');
const account = facade.createAccount(privateKeyObject);
const address = account.address.toString();

// Pepper を .env から取得
const pepper = process.env.PEPPER;
if (!pepper) {
return res.status(500).json({ message: "Internal Server Error: サーバー設定エラー" });
}

const passwordWithPepper = password + pepper;

// 秘密鍵の暗号化
const encryptedPrivateKey = encrypt(passwordWithPepper, privateKey);

// パスワード + Pepper を Hash 化
const hashedPassword = await argon2.hash(passwordWithPepper, {
type: argon2.argon2id,
memoryCost: 2 ** 16, // 推奨: 64MB
timeCost: 5, // 計算回数
parallelism: 1 // 並列数
});

// DB に登録
await DBPerf(
"Insert Into Identify",
"INSERT INTO Identify (UserID, Password, PrivateKey, Address) VALUES (?, ?, ?, ?)",
[userId, hashedPassword, encryptedPrivateKey, address]
);

res.status(200).json({ redirect: "/Home" });
} catch (err) {
console.error("Register Error:", err);
res.status(500).json({ message: "Internal Server Error: サーバーエラーが発生しました。" });
}
});

export default router;
Loading
Loading