KabuTrade のセキュリティアーキテクチャと実装の詳細。
graph TB
subgraph クライアント
Browser[ブラウザ]
Bot[AI/Bot]
end
subgraph セキュリティレイヤー
Helmet[Helmet.js<br/>セキュリティヘッダー]
RateLimit[レート制限<br/>express-rate-limit]
CORS[CORS<br/>オリジン制限]
SizeLimit[ペイロード制限<br/>100KB]
end
subgraph 認証レイヤー
CookieAuth[Cookie認証<br/>JWT + セッション検証]
APIKeyAuth[APIキー認証<br/>SHA-256ハッシュ照合]
RBAC[RBAC<br/>ロールベースアクセス制御]
end
subgraph アプリケーション
Routes[ルートハンドラ]
Validation[Zodバリデーション]
Sanitize[入力サニタイズ]
end
subgraph データストア
DB[(PostgreSQL)]
AuditLog[監査ログ]
end
Browser -->|Cookie| Helmet
Bot -->|Bearer Token| Helmet
Helmet --> RateLimit
RateLimit --> CORS
CORS --> SizeLimit
SizeLimit --> CookieAuth
SizeLimit --> APIKeyAuth
CookieAuth --> RBAC
APIKeyAuth --> RBAC
RBAC --> Routes
Routes --> Validation
Validation --> Sanitize
Sanitize --> DB
Routes -.->|全操作を記録| AuditLog
AuditLog --> DB
Loading
sequenceDiagram
participant C as クライアント
participant S as バックエンド
participant DB as PostgreSQL
rect rgb(240, 248, 255)
Note over C,DB: Web UI ログイン (Cookie認証)
C->>S: POST /api/auth/login {email, password}
S->>S: レート制限チェック (10回/分)
S->>DB: ユーザー検索
alt アカウントロック中
S-->>C: 423 Locked (15分間)
end
S->>S: bcrypt パスワード検証
alt 認証失敗
S->>DB: failedLoginAttempts++
S->>DB: 監査ログ (LOGIN_FAILED, WARNING)
S-->>C: 401 Unauthorized
else 認証成功
S->>S: JWT生成 (7日間有効)
S->>DB: セッション作成 (tokenHash)
S->>DB: failedLoginAttempts = 0
S->>DB: 監査ログ (LOGIN, INFO)
S-->>C: Set-Cookie: token=<jwt>
end
end
rect rgb(255, 248, 240)
Note over C,DB: Bot API (APIキー認証)
C->>S: GET /api/v1/... + Authorization: Bearer kt_xxx
S->>S: レート制限チェック (60回/分)
S->>S: キープレフィックス抽出
S->>DB: keyHash で検索 (SHA-256)
S->>S: パーミッションチェック
S-->>C: レスポンス
end
Loading
evex-accounts OAuth 2.0 フロー
evex-accounts が設定されている場合、OAuth 2.0 Authorization Code + PKCE (S256) フローによるログインが利用可能です。
sequenceDiagram
participant U as ユーザー
participant FE as Frontend
participant BE as Backend
participant EV as evex-accounts
participant DB as PostgreSQL
U->>FE: 「Evex アカウントでログイン」クリック
FE->>BE: GET /api/auth/evex
BE->>BE: PKCE 生成<br/>(code_verifier + code_challenge)
BE->>BE: state 生成 (CSRF対策)
BE->>BE: Cookie に保存<br/>(evex_cv, evex_state)
BE-->>U: 302 Redirect → evex-accounts
U->>EV: 認可画面でログイン & 同意
EV-->>U: 302 Redirect → /api/auth/evex/callback?code=...&state=...
U->>BE: GET /api/auth/evex/callback
BE->>BE: state 検証 (Cookie と比較)
BE->>EV: POST /api/oauth/token<br/>(code + code_verifier)
EV-->>BE: access_token (evex_at_...)
BE->>EV: GET /api/oauth/userinfo<br/>(Bearer access_token)
EV-->>BE: {sub, email, name,<br/>discord_id, discord_roles}
BE->>DB: ユーザー検索/作成<br/>(externalId = sub)<br/>authProvider, discordId, discordRoles 保存
BE->>DB: セッション作成
BE->>DB: 監査ログ (LOGIN, evex-oauth)
BE-->>U: Set-Cookie: token=<jwt><br/>302 Redirect → /trade
Loading
環境変数
説明
EVEX_ACCOUNTS_URL
evex-accounts サーバーURL (例: https://accounts.evex.land)
EVEX_CLIENT_ID
OAuth Client ID
EVEX_CLIENT_SECRET
OAuth Client Secret
EVEX_REDIRECT_URI
コールバックURL (例: http://localhost:4000/api/auth/evex/callback)
スコープ
用途
openid
ユーザーID (sub) の取得
profile
ユーザー名・アイコンの取得
email
メールアドレスの取得
offline_access
リフレッシュトークンの取得
discord_id
Discord ユーザーIDの取得
discord_roles
Discord サーバーロールの取得
項目
値
トークン形式
Opaque (JWT ではない)
プレフィックス
evex_at_ (access) / evex_rt_ (refresh)
Access Token TTL
1時間
Refresh Token TTL
30日 (ローテーション)
保存方式
サーバー側で SHA-256 ハッシュのみ保存
対策
実装
CSRF
state パラメータを HttpOnly Cookie に保存し、コールバック時に検証
PKCE (S256)
code_verifier を Cookie に保存、認可サーバーが code_challenge を検証
Cookie セキュリティ
HttpOnly / Secure (本番) / SameSite=Lax / 5分間有効
クライアント認証
client_secret_post (トークンリクエストのボディに含めて送信)
OAuth ログイン時のローカルユーザー紐付けロジック:
externalId (= evex-accounts の sub) でユーザーを検索
見つからない場合、email で既存ユーザーを検索 → externalId を紐付け
どちらも見つからない場合、新規ユーザーを自動作成 (初期残高付与)
既存ユーザーの名前・メールが変更されていれば自動更新
ログイン時に以下の情報がユーザーレコードに保存されます:
フィールド
説明
authProvider
認証方式 (local / evex)
externalId
evex-accounts の sub クレーム
discordId
Discord ユーザーID (evex-accounts から取得)
discordRoles
Discord サーバーロール一覧 (evex-accounts から取得)
管理画面のユーザー一覧・詳細画面から認証方式と Discord 情報を確認できます。
方式
用途
トークン保存
有効期限
失効方法
Cookie (JWT)
Web UI (ローカル認証)
サーバー側: tokenHash (SHA-256)
7日間
ログアウト / セッション失効
evex-accounts OAuth
Web UI (外部認証)
evex: opaque token / KabuTrade: JWT Cookie
evex: 1h (AT) / KabuTrade: 7日
ログアウト
APIキー
Bot/AI
サーバー側: keyHash (SHA-256)
任意 (無期限可)
削除 / 無効化
graph LR
subgraph ロール
USER[USER]
ADMIN[ADMIN]
end
subgraph エンドポイント
Auth[認証 API]
Trade[取引 API]
Stocks[株価 API]
Account[アカウント API]
APIKeys[APIキー管理]
AdminAPI[管理 API]
end
USER --> Auth
USER --> Trade
USER --> Stocks
USER --> Account
USER --> APIKeys
ADMIN --> Auth
ADMIN --> Trade
ADMIN --> Stocks
ADMIN --> Account
ADMIN --> APIKeys
ADMIN --> AdminAPI
style AdminAPI fill:#ff6b6b,color:#fff
Loading
パーミッション
アクセス可能な操作
read
株価取得、残高確認、注文履歴、ポジション一覧
trade
現物注文の発注
margin
信用取引 (買建・売建・決済)
*
全権限
graph LR
subgraph 入力
PW[パスワード]
JWT[JWTトークン]
AK[APIキー]
end
subgraph ハッシュ処理
BCrypt[bcryptjs<br/>salt rounds: 12]
SHA1[SHA-256]
SHA2[SHA-256]
end
subgraph DB保存
PWH[passwordHash]
TH[tokenHash]
KH[keyHash + keyPrefix]
end
PW --> BCrypt --> PWH
JWT --> SHA1 --> TH
AK --> SHA2 --> KH
style PW fill:#ff6b6b,color:#fff
style JWT fill:#ff6b6b,color:#fff
style AK fill:#ff6b6b,color:#fff
style PWH fill:#51cf66,color:#fff
style TH fill:#51cf66,color:#fff
style KH fill:#51cf66,color:#fff
Loading
データ
ハッシュ方式
備考
パスワード
bcrypt (12 rounds)
不可逆。照合のみ可能
JWTトークン
SHA-256
平文はDBに保存しない
APIキー
SHA-256
作成時に一度だけ表示。keyPrefix (先頭4文字) を別途保存しレート制限に使用
作成 — ランダム生成 → SHA-256ハッシュをDBに保存 → 平文キーをレスポンスで返却 (一度のみ)
認証 — リクエストのキーをSHA-256 → DB上のhashと照合
無効化 — isActive: false に更新、または削除
制約: ユーザーあたり最大10個
エンドポイント
制限
単位
目的
一般 API
100 req/min
IP
DoS防止
認証 (/api/auth/*)
10 req/min
IP
ブルートフォース防止
取引 (/api/trade/*)
30 req/min
IP
過剰注文防止
Bot API (/api/v1/*)
60 req/min
APIキーprefix
Bot用レート制御
APIキー作成
5 req/hour
IP
乱用防止
リクエストボディ上限: 100KB
超過時は 413 Payload Too Large を返却
全てのセキュリティイベントおよび重要な操作は audit_logs テーブルに永続記録されます。
監査ログは削除されません。 コンプライアンスおよびセキュリティ調査のため、全ログを永続保持します。
graph TD
subgraph "INFO (通常操作)"
LOGIN[LOGIN]
LOGOUT[LOGOUT]
ORDER[ORDER]
MARGIN_CLOSE[MARGIN_CLOSE]
API_KEY_CREATED[API_KEY_CREATED]
API_KEY_DELETED[API_KEY_DELETED]
API_KEY_UPDATED[API_KEY_UPDATED]
end
subgraph "WARNING (注意)"
LOGIN_FAILED[LOGIN_FAILED]
PASSWORD_CHANGE[PASSWORD_CHANGE]
ADMIN_BALANCE[ADMIN_BALANCE_ADJUST]
ADMIN_ACTIVATE[ADMIN_ACTIVATE_USER]
end
subgraph "CRITICAL (重大)"
ACCOUNT_LOCKED[ACCOUNT_LOCKED]
ADMIN_ROLE[ADMIN_ROLE_CHANGE]
end
style LOGIN fill:#51cf66,color:#fff
style LOGOUT fill:#51cf66,color:#fff
style ORDER fill:#51cf66,color:#fff
style MARGIN_CLOSE fill:#51cf66,color:#fff
style API_KEY_CREATED fill:#51cf66,color:#fff
style API_KEY_DELETED fill:#51cf66,color:#fff
style API_KEY_UPDATED fill:#51cf66,color:#fff
style LOGIN_FAILED fill:#ffd43b,color:#333
style PASSWORD_CHANGE fill:#ffd43b,color:#333
style ADMIN_BALANCE fill:#ffd43b,color:#333
style ADMIN_ACTIVATE fill:#ffd43b,color:#333
style ACCOUNT_LOCKED fill:#ff6b6b,color:#fff
style ADMIN_ROLE fill:#ff6b6b,color:#fff
Loading
フィールド
型
説明
id
String
CUID
userId
String?
操作者のユーザーID
action
String
操作種別 (上図参照)
resource
String?
対象リソース (order:xxx, user:xxx など)
detail
String?
詳細情報 (JSON)
ipAddress
String?
リクエスト元IP
userAgent
String?
User-Agent
severity
String
INFO / WARNING / CRITICAL
createdAt
DateTime
タイムスタンプ
管理者は GET /api/admin/audit-logs で監査ログを閲覧できます。フィルター:
severity — 重大度でフィルタ
action — 操作種別でフィルタ
userId — ユーザーでフィルタ
page — ページネーション
stateDiagram-v2
[*] --> 通常
通常 --> 通常: ログイン成功<br/>(failedAttempts = 0)
通常 --> 失敗カウント中: ログイン失敗<br/>(failedAttempts++)
失敗カウント中 --> 失敗カウント中: ログイン失敗<br/>(failedAttempts++)
失敗カウント中 --> 通常: ログイン成功<br/>(failedAttempts = 0)
失敗カウント中 --> ロック: failedAttempts >= 5<br/>(CRITICAL ログ記録)
ロック --> 通常: 15分経過
ロック --> ロック: ログイン試行<br/>(423 Locked)
Loading
ロック閾値: 5回連続失敗
ロック時間: 15分間
ロック時の記録: ACCOUNT_LOCKED (severity: CRITICAL)
ルール
要件
最小文字数
12文字
最大文字数
128文字
大文字
1文字以上
小文字
1文字以上
数字
1文字以上
特殊文字
1文字以上
禁止パスワード
password, 12345678, qwerty 等の一般的なパスワードはブロック
全APIエンドポイントで Zod による入力バリデーションを実施。
スキーマ
対象
主な制約
loginSchema
ログイン
email形式、password必須
registerSchema
ユーザー登録
email形式、name、passwordポリシー適用
orderSchema
注文発注
symbol必須、market (JP/US)、side (BUY/SELL)、type、quantity (正の整数)、LIMIT時はprice必須
apiKeyCreateSchema
APIキー作成
name必須、permissions配列、expiresInDays
marginCloseSchema
信用決済
positionId必須
adminUserUpdateSchema
管理者によるユーザー編集
部分更新可、各フィールドの型制約
ユーザー入力はHTMLエンティティエンコーディングでサニタイズし、XSSを防止。
Helmet.js により以下のヘッダーを自動設定:
ヘッダー
効果
Content-Security-Policy
XSS / コードインジェクション防止
Strict-Transport-Security
HTTPS強制 (HSTS)
X-Frame-Options
クリックジャッキング防止
X-Content-Type-Options
MIMEタイプスニッフィング防止
X-XSS-Protection
ブラウザXSSフィルター有効化
Referrer-Policy
リファラー情報の漏洩防止
ディレイのある株価データを悪用したカンニング取引 (表示価格と実際の市場価格の乖離を利用) を防止するため、成行注文に対してスリッページを適用します。
項目
値
スリッページ率
0.05% 〜 0.5%
方向バイアス
70%で不利方向 (買い→上方向、売り→下方向)、30%で有利方向
丸め
日本株: 整数、米国株: 小数2桁
日本株は東証の値幅制限テーブル (34段階) に基づき、前日終値からの値幅制限を適用します。
ストップ高到達時 : 買い注文をブロック
ストップ安到達時 : 売り注文をブロック
QuoteDisplay にストップ高/安価格とバッジを表示
日本株で100株未満の注文は S株として処理し、以下の制約を適用します:
ルール
制約
注文方法
成行注文のみ (指値不可)
取引種別
現物のみ (信用取引不可)
約定価格
前日終値で約定
項目
値
セッション保存
PostgreSQL (sessions テーブル)
トークン保存
SHA-256ハッシュのみ (平文なし)
セッション有効期限
7日間
期限切れセッション清掃
1時間ごと自動実行
全セッション無効化
POST /api/auth/revoke-all-sessions
追跡情報
IPアドレス、User-Agent