Skip to content
Open

done #35

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
75 changes: 57 additions & 18 deletions Backend/NFC-bridge/NFC-bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,91 @@
from smartcard.util import toHexString
import requests
import json
import threading
import time
import re

# カードの状態を監視するクラス
class MyObserver(CardObserver):
def __init__(self, stop_event):
self.stop_event = stop_event

def update(self, observable, actions):
(addedcards, removedcards) = actions
for card in addedcards:
print(f"+ カードを検出しました")
# 接続してUID(識別番号)を取得するコマンドを送信
# [0xFF, 0xCA, 0x00, 0x00, 0x00] は標準的なUID取得コマンドです
print(f"\n+ カードを検出しました")
connection = card.createConnection()
connection.connect()

# 1. UIDの取得
GET_UID = [0xFF, 0xCA, 0x00, 0x00, 0x00]
data, sw1, sw2 = connection.transmit(GET_UID)
uid = toHexString(data)
print(f" UID: {uid}")

# UIDをサーバーに送信する
url = "http://localhost:3000/NFC" # サーバーのURLを指定
payload ={"uid": uid, "status": "detected"}
# 2. NFC内の暗号化データを読み取る
encrypted_payload = None
try:
raw_bytes = []
# 修正: 16バイト(0x10)ずつ読み取るように戻し、4ブロック(ページ)飛ばしでループ
for block in range(4, 32, 4):
READ_CMD = [0xFF, 0xB0, 0x00, block, 0x10]
read_data, r_sw1, r_sw2 = connection.transmit(READ_CMD)

if r_sw1 == 0x90 and r_sw2 == 0x00:
raw_bytes.extend(read_data)
else:
# ★ここが重要!読み取り拒否された場合のエラーコードを出力
print(f" [Error] ブロック {block} の読み取り失敗: SW1={hex(r_sw1)}, SW2={hex(r_sw2)}")

read_text = bytes(raw_bytes).decode('ascii', errors='ignore')
print(f" [Debug] 読み取った生テキスト: {read_text}")

match = re.search(r'([0-9a-f]{24}):([0-9a-f]{8}):([0-9a-f]{32})', read_text)

encrypted_payload = None
if match:
encrypted_payload = match.group(0)
print(f" ★パスワードデータ検出: {encrypted_payload}")
else:
print(" 指定フォーマットのパスワードデータが見つかりませんでした。")

except Exception as e:
print(f" データ読み取り中にエラーが発生しました: {e}")

# 3. UIDと「文字列のパスワード」をサーバーに送信
url = "http://localhost:5000/NFC"
payload = {
"uid": uid,
"status": "detected",
"encrypted_password": encrypted_payload # 文字列("iv:data:tag")をそのまま送る
}
print(f" [Debug] 送信するJSONデータ: {payload}")

try:
response = requests.post(url, json=payload)

if response.status_code == 200:
print("サーバーに送信しました")
print(" サーバーに送信しました")
self.stop_event.set()
else:
print(f"サーバーへの送信に失敗しました: {response.status_code}")
print(f" サーバーへの送信に失敗しました: {response.status_code}")
except requests.RequestException as e:
print(f"通信エラー: {e}")
print(f" 通信エラー: {e}")

for card in removedcards:
print("- カードが離されました")

# 監視の開始
if __name__ == "__main__":
print("カードリーダーを待機中...")
monitor = CardMonitor()
observer = MyObserver()
stop_event = threading.Event()
observer = MyObserver(stop_event)
monitor.addObserver(observer)

try:
# プログラムを実行し続ける
import time
while True:
while not stop_event.is_set():
time.sleep(1)
except KeyboardInterrupt:
# Ctrl+Cで停止
monitor.removeObserver(observer)
pass
finally:
monitor.deleteObserver(observer)
print("終了します")
55 changes: 54 additions & 1 deletion Backend/Workspace/Routes/CreateRoom.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import VCM from '../Tools/VCM.js';
import DBPerf from '../Tools/DBPerf.js';
import { decrypt } from '../Tools/AESControl.js';
import { CreateMosaicTx } from '../Tools/CreateMosaicTx.js';
import { CreateSupplyTx } from '../Tools/SupplyMosaic.js';
import SignAndAnnounce from '../Tools/SignAndAnnounce.js';

// 注意2: ESMでは __dirname がデフォルトで存在しないため、自作する必要があります
const __filename = fileURLToPath(import.meta.url);
Expand Down Expand Up @@ -76,13 +78,64 @@ router.post("/", VCM('LOGIN_TOKEN', process.env.LOGIN_SECRET), upload.fields([{
const encryptedPrivateKeyObj = JSON.parse(OwnerInfor[0].PrivateKey);
const privateKey = decrypt(inputPassword + process.env.PEPPER, encryptedPrivateKeyObj);

// ===== Mosaic定義トランザクション作成 =====
const { mosaicId, mosaicDefinitionTx, keyPair, facade } = CreateMosaicTx({
networkType: 'testnet',
senderPrivateKey: privateKey,
transferable: false,
transferable: true,
deadlineHours: 24
});

// ===== DBに保存する前に、モザイクをブロックチェーンに登録 =====
try {
console.log("[CreateRoom] Announcing Mosaic Definition Transaction...");
const definitionResult = await SignAndAnnounce(
mosaicDefinitionTx,
privateKey,
facade,
'https://sym-test-01.opening-line.jp:3001',
{
waitForConfirmation: true,
confirmationTimeoutMs: 180000,
pollIntervalMs: 2000
}
);
console.log("[CreateRoom] Mosaic Definition TX Hash:", definitionResult.hash);
console.log("[CreateRoom] Mosaic Definition TX Announced Successfully!");

// ===== 供給量設定トランザクション作成・送信 =====
console.log("[CreateRoom] Creating Supply Change Transaction...");
const { supplyTx, keyPair: supplyKeyPair, facade: supplyFacade } = CreateSupplyTx({
networkType: 'testnet',
senderPrivateKey: privateKey,
mosaicId: mosaicId,
supply: 1_000_000n, // 初期供給量:100万(divisibility=0なので実際の量)
deadlineHours: 24
});

console.log("[CreateRoom] Announcing Supply Change Transaction...");
const supplyResult = await SignAndAnnounce(
supplyTx,
privateKey,
supplyFacade,
'https://sym-test-01.opening-line.jp:3001',
{
waitForConfirmation: true,
confirmationTimeoutMs: 180000,
pollIntervalMs: 2000
}
);
console.log("[CreateRoom] Supply Change TX Hash:", supplyResult.hash);
console.log("[CreateRoom] Supply Change TX Announced Successfully!");

} catch (txErr) {
console.error("[CreateRoom] Blockchain Transaction Error:", txErr);
return res.status(500).json({
message: "Failed to register mosaic on blockchain",
error: txErr.message
});
}

const RoomIconPath = await saveIcon(req.files.RoomIcon[0], "rooms");
console.log("RoomIcon saved at:", RoomIconPath);
const MosaicIconPath = await saveIcon(req.files.MosaicIcon[0], "tokens");
Expand Down
54 changes: 54 additions & 0 deletions Backend/Workspace/Routes/JoinRoom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import express from 'express';
import DBPerf from '../Tools/DBPerf.js';
import VCM from '../Tools/VCM.js';

const router = express.Router();

router.use(express.json());

router.post('/' , VCM('LOGIN_TOKEN', process.env.LOGIN_SECRET), async (req, res) => {
console.log('/JoinRoom-API is running');

try {
const userId = req.auth.userId;
const inputRoomName = req.body?.roomName;
const roomName = typeof inputRoomName === 'string' ? inputRoomName.trim() : '';

if (!roomName) {
return res.status(400).json({ message: 'Bad Request: roomNameが不足しています' });
}

const roomExists = await DBPerf(
'Check Room Exists',
'SELECT RoomName FROM RoomDetails WHERE RoomName = ?',
[roomName]
);

if (roomExists.length === 0) {
return res.status(404).json({ message: 'Not Found: 指定されたルームは存在しません' });
}

const alreadyJoined = await DBPerf(
'Check Already Joined',
'SELECT 1 FROM Rooms WHERE UserID = ? AND RoomName = ?',
[userId, roomName]
);

if (alreadyJoined.length > 0) {
return res.status(409).json({ message: 'Conflict: 既にこのルームに所属しています' });
}

await DBPerf(
'Insert Room Member',
'INSERT INTO Rooms (UserID, RoomName) VALUES (?, ?)',
[userId, roomName]
);

return res.status(201).json({ message: 'ルーム参加が完了しました' });
} catch (err) {
console.error('JoinRoom-API Error:', err);
return res.status(500).json({ message: 'Internal Server Error' });
}
});

export default router;
2 changes: 1 addition & 1 deletion Backend/Workspace/Routes/Login.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ router.get(
* - DBからユーザー情報を取得
* - ハッシュ検証後にクッキーを発行
*/
router.post("/Submit", InverseVCM('LOGIN_TOKEN', process.env.LOGIN_SECRET), async (req, res) => {
router.post("/Submit", async (req, res) => {

// 0. 処理開始ログ
console.log("/Login/Submit-API is running!");
Expand Down
77 changes: 49 additions & 28 deletions Backend/Workspace/tmp/NFC.js → Backend/Workspace/Routes/NFC.js
Original file line number Diff line number Diff line change
@@ -1,75 +1,96 @@
const express = require('express');
const path = require('path');
const dotenv = require('dotenv').config();
const DBPerf = require('../Tools/DBPerf');
const argon2 = require('argon2');
import express from 'express';
import path from 'path'; // ※現在のコードでは使用されていないようですが残しています
import dotenv from 'dotenv';
import DBPerf from '../Tools/DBPerf.js'; // ESMでは自作モジュールに拡張子(.js)が必須です
import argon2 from 'argon2';

dotenv.config();

const router = express.Router();

let latestcardUid = null; // 最新のカードUIDを保存する変数
let latestcardEncryptedPassword = null; // 最新のカードの暗号化されたパスワードを保存する変数
let latestcardTime = null; // 最新のカード検知時間を保存する変数

// NFCカードの紐づけエンドポイント
router.post('/NFC/Submit', async(req, res) => {
try{
const {userID, password} = req.body
const nfcRead = false; // NFCカードが読み取られたかのフラグ(実際にはNFCStateなどで管理する)
router.post('/Submit', async (req, res) => {
// 0. 処理開始ログ
console.log("/NFC/Submit-API is running!");
try {
const { userID, userId, password } = req.body;
const normalizedUserID = userID ?? userId;
// 【修正】後で true を代入するため、const ではなく let に変更しました
let nfcRead = false;

if(!latestcardTime || Date.now() - latestcardTime > 10000){
if (!latestcardTime || Date.now() - latestcardTime > 10000) {
return res.status(400).send("カードをもう一度かざしてください");
}

// ユーザーIDとパスワードが送られてきているかの確認
if(!userID || !password){
if (!normalizedUserID || !password) {
console.log("ユーザーIDまたはパスワードが送られてきていません!");
return res.status(400).send({ message: 'User ID and password are required' });
}

// カードUIDが送られてきているかの確認
if(!latestcardUid){
if (!latestcardUid) {
console.log("カードのUIDが送られてきていません!");
return res.status(400).send({ message: 'Card UID is required' });
}

// カードが既に紐づけられていないかの確認
const exitUid = await DBPerf("カードが既に紐づけられていないかの確認","SELECT * FROM NFC WHERE UID = ?",[latestcardUid])
if(exitUid.length != 0){
console.log("このカードは既に紐づけられています!");
return res.status(400).send({ message: 'This card is already linked to an account' });
const exitUid = await DBPerf("カードが既に紐づけられていないかの確認", "SELECT * FROM NFC WHERE UID = ?", [latestcardUid]);
if (exitUid.length != 0) {
await DBPerf(
"カード上書き",
`INSERT INTO NFC (UID, UserID) VALUES (?, ?)
ON DUPLICATE KEY UPDATE UserID = VALUES(UserID)`,
[latestcardUid, normalizedUserID]
);

console.log("このカードのアカウントを上書きしました");
return res.status(200).send({ message: 'This card is updated' });
}

// ユーザーIDが存在するかの確認とパスワードの照合
const userInfor = await DBPerf("存在するアカウントかの確認","SELECT userID, password FROM users WHERE userID = ?",[userID])
const userInfor = await DBPerf("存在するアカウントかの確認", "SELECT UserID, Password FROM Identify WHERE UserID = ?", [normalizedUserID]);

// ユーザーIDが存在しない場合はダミーパスワードと照合して常に失敗させる(セキュリティ対策)
const comparePassword = userInfor.length == 0 ? process.env.DUMMY_PASSWORD : userInfor[0].password;
if (await argon2.verify(comparePassword, password + process.env.PEPPER)){
const comparePassword = userInfor.length == 0 ? process.env.DUMMY_PASSWORD : userInfor[0].Password;

if (await argon2.verify(comparePassword, password + process.env.PEPPER)) {
// アカウントとNFCカードの紐づけ
await DBPerf("アカウントとNFCカードの紐づけ","INSERT INTO NFC (UID, userID) VALUES (?, ?)",[latestcardUid, userID])
console.log(`カードUID: ${latestcardUid} とユーザーID: ${userID} を紐づけました!`);
await DBPerf("アカウントとNFCカードの紐づけ", "INSERT INTO NFC (UID, UserID) VALUES (?, ?)", [latestcardUid, normalizedUserID]);
console.log(`カードUID: ${latestcardUid} とユーザーID: ${normalizedUserID} を紐づけました!`);
latestcardUid = null;
latestcardTime = null;
nfcRead = true;
res.json({nfcRead: nfcRead});
}
else{
res.json({ nfcRead: nfcRead });
} else {
console.log("パスワードが間違っています!");
return res.status(401).send({ message: 'Invalid password' });
}
}catch(err){
} catch (err) {
console.error("NFCカードの紐づけに失敗しました!", err);
return res.status(500).send({ message: 'Failed to link NFC card' });
}
});

// PythonからカードUIDを受け取るエンドポイント
router.post('/NFC', (req, res) => {
router.post('/', (req, res) => {
latestcardUid = req.body.uid; // Pythonから送られてきたUID
latestcardEncryptedPassword = req.body.encrypted_password; // Pythonから送られてきた暗号化されたパスワードオブジェクト
latestcardTime = Date.now(); // カードが検知された時間を保存
console.log(`カードを検知しました! UID: ${latestcardUid}`);
console.log(`暗号化されたパスワードデータ: ${JSON.stringify(latestcardEncryptedPassword)}`);
globalThis.io?.emit('nfc:detected', {
uid: latestcardUid,
encrypted_password: latestcardEncryptedPassword,
at: latestcardTime
});

// Python側に「無事に受け取ったよ」と返事をする
res.status(200).send({ message: 'Success' });
});

module.exports = router;

export default router;
Loading
Loading