Skip to content
Merged

Done #32

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
116 changes: 116 additions & 0 deletions Backend/Workspace/Routes/CreateRoom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import express from 'express';
import jwt from 'jsonwebtoken';
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

jwt is imported but not used in this new file, which adds noise and can confuse readers. Remove the unused import.

Suggested change
import jwt from 'jsonwebtoken';

Copilot uses AI. Check for mistakes.
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 { randomUUID } from "crypto";

// 注意1: Node.jsのESMでは、独自ファイルのimportに拡張子(.js)が必須です
import VCM from '../Tools/VCM.js';
import DBPerf from '../Tools/DBPerf.js';
import { decrypt } from '../Tools/AESControl.js';
import { CreateMosaicTx } from '../Tools/CreateMosaicTx.js';

// 注意2: ESMでは __dirname がデフォルトで存在しないため、自作する必要があります
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const router = express.Router();

// cookieを使う
router.use(cookieParser());
dotenv.config({ path: path.join(__dirname, "..", ".env")});
const upload = multer({ storage: multer.memoryStorage() });
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

Using multer.memoryStorage() without limits allows large uploads to be fully buffered in memory (DoS risk). Configure strict limits (e.g., limits: { fileSize, files }) and consider disk storage/streaming if files can be large.

Suggested change
const upload = multer({ storage: multer.memoryStorage() });
const upload = multer({
storage: multer.memoryStorage(),
limits: {
fileSize: 1 * 1024 * 1024, // 最大1MB
files: 1 // 1リクエストあたり1ファイル
}
});

Copilot uses AI. Check for mistakes.

// ===アイコン保存処理===
function saveIcon(file, folder) {
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

mkdirSync/writeFileSync blocks the Node.js event loop inside a request handler, which can reduce throughput under load. Prefer async fs.promises.mkdir/fs.promises.writeFile (or streaming) and await them in the route.

Copilot uses AI. Check for mistakes.
const fileName = createFileName(file.originalname);
const dir = path.join(__dirname, "..", "icons", folder);

// フォルダが存在しない場合は作成
fs.mkdirSync(dir, { recursive: true });
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

mkdirSync/writeFileSync blocks the Node.js event loop inside a request handler, which can reduce throughput under load. Prefer async fs.promises.mkdir/fs.promises.writeFile (or streaming) and await them in the route.

Copilot uses AI. Check for mistakes.

// ファイルを保存
const fullPath = path.join(dir, fileName);
fs.writeFileSync(fullPath, file.buffer);
Comment on lines +29 to +38
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

mkdirSync/writeFileSync blocks the Node.js event loop inside a request handler, which can reduce throughput under load. Prefer async fs.promises.mkdir/fs.promises.writeFile (or streaming) and await them in the route.

Suggested change
function saveIcon(file, folder) {
const fileName = createFileName(file.originalname);
const dir = path.join(__dirname, "..", "icons", folder);
// フォルダが存在しない場合は作成
fs.mkdirSync(dir, { recursive: true });
// ファイルを保存
const fullPath = path.join(dir, fileName);
fs.writeFileSync(fullPath, file.buffer);
async function saveIcon(file, folder) {
const fileName = createFileName(file.originalname);
const dir = path.join(__dirname, "..", "icons", folder);
// フォルダが存在しない場合は作成
await fs.promises.mkdir(dir, { recursive: true });
// ファイルを保存
const fullPath = path.join(dir, fileName);
await fs.promises.writeFile(fullPath, file.buffer);

Copilot uses AI. Check for mistakes.

console.log(`Icon saved to ${fullPath}`);

// DBに入れる用の「相対パス」
return `/icons/${folder}/${fileName}`;
}

// ===被らないファイル名を作成===
function createFileName(originalName) {
//拡張子取り出し(extname: .pngなど)
const ext = path.extname(originalName);
const Refilename = `${randomUUID()}${ext}`;
Comment on lines +47 to +50
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

Deriving the stored extension directly from user-supplied filenames can allow uploading and later serving potentially dangerous file types (e.g., .html, .svg) if /icons is publicly served. Enforce an allowlist based on MIME type and/or content sniffing (e.g., only png/jpg/webp), and normalize extension accordingly.

Copilot uses AI. Check for mistakes.
console.log(`Generated unique filename: ${Refilename}`);
//タイムスタンプを付与して被らないようにする
return Refilename;
Comment on lines +50 to +53
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

Refilename uses PascalCase in the middle of a function where other locals are camelCase (fileName, originalName, ext). Rename to refilename/fileName/uniqueFileName to match common JS conventions and improve readability.

Suggested change
const Refilename = `${randomUUID()}${ext}`;
console.log(`Generated unique filename: ${Refilename}`);
//タイムスタンプを付与して被らないようにする
return Refilename;
const uniqueFileName = `${randomUUID()}${ext}`;
console.log(`Generated unique filename: ${uniqueFileName}`);
//タイムスタンプを付与して被らないようにする
return uniqueFileName;

Copilot uses AI. Check for mistakes.
}

// ===ルーム作成API===
router.post("/", VCM('LOGIN_TOKEN', process.env.LOGIN_SECRET), upload.fields([{ name: "RoomIcon", maxCount: 1 },{ name: "MosaicIcon", maxCount: 1 }]), async (req, res) => {
try {
const userID = req.auth.userId;
const { RoomName, MosaicName, Password } = req.body;
console.log("Received CreateRoom request:", { userID, RoomName, MosaicName, Password });
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

This logs Password from the request body, which is highly sensitive. Avoid logging credentials; log only non-sensitive request metadata (e.g., userID and presence/validation results).

Suggested change
console.log("Received CreateRoom request:", { userID, RoomName, MosaicName, Password });
console.log("Received CreateRoom request:", { userID, RoomName, MosaicName, PasswordProvided: Boolean(Password) });

Copilot uses AI. Check for mistakes.
if (!userID || !RoomName || !MosaicName || !Password) {
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

Request validation is duplicated and partially overlapping (RoomName/MosaicName checked twice; icons validated separately). Consolidate into a single validation block that checks all required fields/files once so future edits don’t diverge.

Copilot uses AI. Check for mistakes.
console.log("Missing required fields in CreateRoom request");
return res.status(400).json({ message: "UserID, RoomName, MosaicName, and Password are required" });
}


if (!RoomName || !MosaicName || !req.files?.RoomIcon || !req.files?.MosaicIcon) {
return res.status(400).json({ message: "Bad Request" });
Comment on lines +62 to +69
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

Request validation is duplicated and partially overlapping (RoomName/MosaicName checked twice; icons validated separately). Consolidate into a single validation block that checks all required fields/files once so future edits don’t diverge.

Suggested change
if (!userID || !RoomName || !MosaicName || !Password) {
console.log("Missing required fields in CreateRoom request");
return res.status(400).json({ message: "UserID, RoomName, MosaicName, and Password are required" });
}
if (!RoomName || !MosaicName || !req.files?.RoomIcon || !req.files?.MosaicIcon) {
return res.status(400).json({ message: "Bad Request" });
const hasRequiredBody = !!(userID && RoomName && MosaicName && Password);
const hasRequiredFiles =
!!(req.files &&
req.files.RoomIcon && req.files.RoomIcon[0] &&
req.files.MosaicIcon && req.files.MosaicIcon[0]);
if (!hasRequiredBody || !hasRequiredFiles) {
console.log("Missing required fields or files in CreateRoom request");
const message = !hasRequiredBody
? "UserID, RoomName, MosaicName, and Password are required"
: "Bad Request";
return res.status(400).json({ message });

Copilot uses AI. Check for mistakes.
}

const OwnerInfor = await DBPerf(
"Get Encrypted Private Key","SELECT PrivateKey FROM Identify WHERE userID = ?",[userID]
);
const encryptedPrivateKeyObj = JSON.parse(OwnerInfor[0].PrivateKey);
const privateKey = decrypt(Password + process.env.PEPPER, encryptedPrivateKeyObj);

const { mosaicId, mosaicDefinitionTx, keyPair, facade } = CreateMosaicTx({
networkType: 'testnet',
senderPrivateKey: privateKey,
transferable: false,
deadlineHours: 24
});

const RoomIconPath = await saveIcon(req.files.RoomIcon[0], "rooms");
console.log("RoomIcon saved at:", RoomIconPath);
const MosaicIconPath = await saveIcon(req.files.MosaicIcon[0], "tokens");
console.log("MosaicIcon saved at:", MosaicIconPath);

await DBPerf(
"INSERT Mosaic",
"INSERT INTO Mosaic (MosaicID, MosaicName) VALUES (?, ?)",
[mosaicId, MosaicName ]
);

await DBPerf(
"INSERT RoomDetails",
"INSERT INTO RoomDetails(RoomName, RoomIconPath, MosaicName) VALUES (?, ?, ?)",
[RoomName, RoomIconPath, MosaicName]
);

await DBPerf(
"INSERT Rooms",
"INSERT INTO Rooms(UserID, RoomName) VALUES (?, ?)",[userID, RoomName]
);

res.status(201).json({ message: "Room created successfully" });
} catch (err) {
console.error("CreateRoom-API Error:", err);
res.status(500).json({ message: "Internal Server Error" });
}
}
);

// モジュールのエクスポート
export default router;
7 changes: 5 additions & 2 deletions Backend/Workspace/Routes/Register.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,11 @@ router.post(

// ユーザーのパスワードから復号可能にする設計
const encryptedPrivateKey =
encrypt(passwordWithPepper, privateKeyString);

JSON.stringify(
encrypt(passwordWithPepper, privateKeyString)
);
console.log("暗号化された秘密鍵オブジェクト:", privateKeyString);
console.log("暗号化された秘密鍵:", encryptedPrivateKey);

Comment on lines +163 to 165
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

Line 163 appears to log privateKeyString (plaintext private key) under an 'encrypted' label, which leaks sensitive material. Remove logging of plaintext/encrypted key material; if debugging is required, log only non-sensitive properties (e.g., presence, byte lengths) and ensure it's disabled in production.

Suggested change
console.log("暗号化された秘密鍵オブジェクト:", privateKeyString);
console.log("暗号化された秘密鍵:", encryptedPrivateKey);
// 本番環境では秘密鍵の内容をログ出力しない
if (process.env.NODE_ENV !== 'production') {
console.log("暗号化された秘密鍵が生成されました。長さ:", encryptedPrivateKey.length);
}

Copilot uses AI. Check for mistakes.
// =====================================================
// 4. パスワードをArgon2でハッシュ化
Expand Down
100 changes: 100 additions & 0 deletions Backend/Workspace/Tools/CreateMosaicTx.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*========== Manual ==========
# Input(obj)
networkType: mainnet or testnet
senderPrivateKey: 送り元の秘密鍵
transferable: 発行者以外が送信できるかどうか(true or false)
deadlineHours: 有効期限[h]

# Output
mosaicId: 発行したMosaicIDの16進数表現
mosaicDefinitionTx: 実際のトランザクション
keyPair: 署名時に必要な秘密鍵/公開鍵
facade: mainnet or testnetの指定をしているがそれが一貫性を保てるように引き継ぐ
========== Manual ==========*/

// CreateMosaicTx.js
import { PrivateKey } from 'symbol-sdk';
import { SymbolFacade } from 'symbol-sdk/symbol';
import { generateMosaicId } from 'symbol-sdk/symbol';

export function CreateMosaicTx({
networkType = 'testnet',
senderPrivateKey,
transferable = true,
deadlineHours = 2
}) {
// Startup Log
const logOwner = "CreateMosaicTx";
console.log(`\n${logOwner}-Function is running!\n`);
console.log(`[${logOwner}] Input => networkType: ${networkType}, transferable: ${transferable}, deadlineHours: ${deadlineHours}`);

if (!senderPrivateKey) {
throw new Error("senderPrivateKey is undefined");
}

console.log("senderPrivateKey:", senderPrivateKey);
console.log("type:", typeof senderPrivateKey);
console.log("length:", senderPrivateKey?.length);
Comment on lines +35 to +37
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

This logs the plaintext private key to server logs, which is a critical secret exposure. Remove these logs entirely (or replace with non-sensitive diagnostics such as whether the value is present) to avoid leaking keys via logs/monitoring.

Suggested change
console.log("senderPrivateKey:", senderPrivateKey);
console.log("type:", typeof senderPrivateKey);
console.log("length:", senderPrivateKey?.length);
console.log(`[${logOwner}] senderPrivateKey provided: ${senderPrivateKey ? 'yes' : 'no'}`);

Copilot uses AI. Check for mistakes.

// Facade 初期化
const facade = new SymbolFacade(networkType);

// 秘密鍵 → KeyPair
const normalizedPrivateKey = senderPrivateKey.trim();
const privateKeyObject = new PrivateKey(normalizedPrivateKey);
const keyPair = facade.createAccount(privateKeyObject);

// Deadline 作成
const deadline = facade.network
.fromDatetime(new Date())
.addHours(Number(deadlineHours))
.timestamp;

console.log(`[${logOwner}] Intermediate => KeyPair created, Deadline calculated`);

// 0〜2^32-1 のランダムnonce生成
const nonce = Math.floor(Math.random() * 0xffffffff);
Comment on lines +55 to +56
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

Math.random() isn’t ideal for values that contribute to identifiers and should be unpredictable. Prefer a cryptographically strong source (e.g., crypto.randomBytes(4) / crypto.getRandomValues) and convert to an unsigned 32-bit integer.

Copilot uses AI. Check for mistakes.

console.log(`[${logOwner}] Intermediate => Nonce generated: ${nonce}`);

// Mosaic定義トランザクション作成
const mosaicDefinitionTx = facade.transactionFactory.create({
type: 'mosaic_definition_transaction_v1',
signerPublicKey: keyPair.publicKey,
duration: 0,
nonce: nonce,
flags: {
supplyMutable: true,
transferable: transferable,
restrictable: false,
revocable: false,
},
divisibility: 0,
deadline: deadline
});

console.log(`[${logOwner}] Intermediate => MosaicDefinitionTx created with type: ${mosaicDefinitionTx.type}`);


const ownerAddress = facade.network.publicKeyToAddress(keyPair.publicKey);
// MosaicID計算
const mosaicIdBigInt = generateMosaicId(ownerAddress, nonce);
const mosaicIdHex = mosaicIdBigInt.toString(16).toUpperCase().padStart(16, '0');

console.log(`[${logOwner}] Output =>
Type: ${mosaicDefinitionTx.type}
SupplyMutable: ${mosaicDefinitionTx.flags.supplyMutable}
Transferable: ${mosaicDefinitionTx.flags.transferable}
MosaicID: ${mosaicIdHex}
`);

console.log(`[${logOwner}] Shutdown!`);

return {
mosaicId: mosaicIdHex,
mosaicDefinitionTx,
keyPair,
facade
};
}

17 changes: 16 additions & 1 deletion Backend/node_modules/.bin/baseline-browser-mapping

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 16 additions & 1 deletion Backend/node_modules/.bin/browserslist

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 16 additions & 1 deletion Backend/node_modules/.bin/esparse

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 16 additions & 1 deletion Backend/node_modules/.bin/esvalidate

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 16 additions & 1 deletion Backend/node_modules/.bin/glob

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 16 additions & 1 deletion Backend/node_modules/.bin/import-local-fixture

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 16 additions & 1 deletion Backend/node_modules/.bin/jest

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 16 additions & 1 deletion Backend/node_modules/.bin/js-yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading