こんにちは。ついにchatGPTに課金(誤用)してしまったミツコアです。最近はとーくんるーむをもうちょい使い勝手よくしようと、とーくんるーむV2を作らんとしています。

utyuneko.jpg
知らない概念と単語がいっぱい出てきて宇宙猫になる私の図

ブログシステムから作 らせるよ!

えー、私が使ったことのあるぱしょこん系の言語といえばもっぱらHTML,CSS,(ほんのちょっとだけ)Jquery程度なのでPHPやらNode.jsなどはさっぱりでした。
でもchatGPT-4oくんなら…!君ならやってくれるだろ…!と対話をしまくった結果、なんとなくできあがってきております。

引っかかったところその1

特に難儀だったのは必要トークン数の判定可否でした。 直して!って言っても全然変わらないのでなんでだろうな~と2日ほど悩んでいた結果、どうも記事単位ではなく一覧のすべての範囲から結果を返してたっぽいです。AIってそういうとこあるよな。

フロントエンド実装の壁を越えろ!

次に、さすがにgetAddressログインではよくないんだろうな~と思い始めたので、署名ログインを実装することにしました。
が、直接的なドキュメントがあんまりない!
きっと似たような実装を参考にMpurse系サービス開発者の人はしてると思うんですが、こちらは素人すぎて調べるための知識すらない…!
でも!きっとchatGPTくんなら…!と思ってこちらを参考にしろと言ってぶん投げました。
Mpurseの署名機能を使って、ユーザID、パスワード、クレカ不要の認証機能の実装
ここのサービスとともに、つくづくお世話になっております…。

引っかかったところその2

参考例から作らせてみたところ、
Login to my application: 1721615078758
というメッセージがページ上に出てしまうので、そうではなく、サーバーサイドで処理してもらうところですごい時間がかかりました。
image.png
結論を言うとこのへんを指定してなかったからっぽいです。ソースコード読めないマンにはコレガワカラナイ。指摘してよ!!
あと試行錯誤した結果node.jsのモジュールにいっぱいフォルダがあるの気になるんだけど、何が不要必要かわからなくて消すに消せない…。

というわけでとりあえずローカルで署名ログインを実装することはできました。次は本番で動くかが心配ですが…とりあえず完成してからあとで考えるか…。
以下、作らせたソースコードを置いてみます。なんかここおかしくね?とかあったらご指摘お願いします。(ファイルパスはちょっと変えてます)

server.js

const express = require('express');
const bodyParser = require('body-parser');
const bitcoinMessage = require('bitcoinjs-message');
const cors = require('cors');
const fs = require('fs');
const https = require('https');
const rateLimit = require('express-rate-limit');
const csurf = require('csurf');
const cookieParser = require('cookie-parser');

const app = express();
const messagePrefix = "\x19Monacoin Signed Message:\n"; // Monacoinのメッセージプレフィックス

// レートリミットの設定
const limiter = rateLimit({
    windowMs: 15 * 60 * 1000, // 15分間に
    max: 100, // 最大100リクエスト
    message: 'Too many requests from this IP, please try again later.'
});

const csrfProtection = csurf({ cookie: true });

app.use(bodyParser.json());
app.use(cookieParser());
app.use(cors({
    origin: 'https://localhost', // 許可するオリジンを設定
    methods: ['GET', 'POST'], // 許可するHTTPメソッド
    allowedHeaders: ['Content-Type', 'CSRF-Token'], // 許可するヘッダー
    credentials: true // クレデンシャル(cookie、認証ヘッダー、TLSクライアント証明書)を含める
}));
app.use(limiter);
app.use(csrfProtection);

var options = {
  key: fs.readFileSync('C:/Program Files/laragon/etc/ssl/laragon.key'),
  cert: fs.readFileSync('C:/Program Files/laragon/etc/ssl/laragon.crt')
};

var server = https.createServer(options, app);

app.get('/csrf-token', (req, res) => {
  res.json({ csrfToken: req.csrfToken() });
});

app.post('/verify', function (req, res) {
  try {
    var date = new Date();
    var currentTime = date.getTime();
    var address = req.body.address;
    var message = req.body.message;
    var signature = req.body.signature;

    console.log('Received address:', address);
    console.log('Received message:', message);
    console.log('Received signature:', signature);

    var result = message.split(':');
    var time_diff = currentTime - parseInt(result[1], 10);

    console.log('Current time:', currentTime);
    console.log('Message time:', parseInt(result[1], 10));
    console.log('Time difference:', time_diff);

    var is_verify = bitcoinMessage.verify(message, address, Buffer.from(signature, 'base64'), messagePrefix);
    console.log('Verification result:', is_verify);

    if (time_diff < 60000 && is_verify) { // 60000ms = 60 seconds
      res.json({ message: true });
    } else {
      res.json({ message: false });
    }
  } catch (error) {
    console.error('Error during verification:', error);
    res.status(500).json({ error: 'Internal Server Error', details: error.message });
  }
});

server.listen(3000, () => {
  console.log('Server is listening on port 3000');
});

test.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Mpurse Login</title>
</head>
<body>
    <h2>Mpurse Login</h2>
    <button id="sign-button" onclick="signMessage()">Sign Message with Mpurse</button>
    <p id="response"></p>
    <script>
        async function getCsrfToken() {
            const response = await fetch('https://localhost:3000/csrf-token', {
                method: 'GET',
                credentials: 'include' // クレデンシャルを含める
            });
            const data = await response.json();
            return data.csrfToken;
        }

        function generateMessage() {
            return `Login to my application: ${Date.now()}`;
        }

        async function signMessage() {
            const message = generateMessage();
            if (!window.mpurse) {
                alert('Mpurse is not available');
                return;
            }
            const address = await window.mpurse.getAddress();
            const signature = await window.mpurse.signMessage(message);
            submitForm(address, signature, message);
        }

        async function submitForm(address, signature, message) {
            try {
                const csrfToken = await getCsrfToken(); // CSRFトークンの取得
                const response = await fetch('https://localhost:3000/verify', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'CSRF-Token': csrfToken // CSRFトークンの送信
                    },
                    credentials: 'include', // クレデンシャルを含める
                    body: JSON.stringify({
                        address: address,
                        signature: signature,
                        message: message
                    })
                });

                if (!response.ok) {
                    const errorText = await response.text();
                    throw new Error(`HTTP error! Status: ${response.status} - ${errorText}`);
                }

                const result = await response.json();
                if (result.message) {
                    document.getElementById('response').innerText = 'ログイン成功';
                } else {
                    document.getElementById('response').innerText = 'ログイン失敗';
                }
            } catch (error) {
                console.error('Error:', error);
                document.getElementById('response').innerText = `エラーが発生しました: ${error.message}`;
            }
        }
    </script>
</body>
</html>