アカウントセキュリティとシステム設定
Castor はログイン状態をサーバー側で管理するため、セッションの一覧表示と強制ログアウトができます。そのうえで 2段階認証、メールによるパスワード再設定、パスワードのルール、レート制限を提供します。利用のハードルを上げる機能は既定で無効になっており、管理者が「システム管理 → システム構成 → システム設定」で必要に応じて有効にします。
システム設定
「システム設定」ページには、実行中に変更できる機能スイッチとパラメーターがまとまっています。system_settings テーブルに保存され、再起動なしで数秒以内に反映されます。ページは「セキュリティ」「メール」「ファイル保存」「AI」の 4 つのタブに分かれています。メール・ファイル保存・AI の項目は設定 → システム設定で行う設定を参照してください。ここでは「セキュリティ」タブを説明します。
| 設定項目 | 用途 | 既定値 |
|---|---|---|
security.totp_enabled | 2段階認証の全体スイッチ | 無効 |
security.totp_required_roles | 2段階認証を必須にするロール | なし |
security.password_reset_enabled | メールによるパスワード再設定 | 無効 |
security.password_min_length | パスワードの最小文字数(6–64) | 6 |
security.password_require_letters_digits | 英字と数字の両方を必須にする | 無効 |
security.password_require_symbol | 記号を必須にする | 無効 |
security.session_ttl_hours | ログインの有効期間(時間、1–720、スライド延長) | SESSION_TTL_HOURS(8) |
security.login_max_failures / security.login_lockout_minutes | ロックまでの失敗回数(3–1000)/ ロック時間兼失敗を数える期間(分、1–1440)。ログイン失敗のロックを参照 | 10 / 15 |
security.rate_limit_per_minute | IP ごとの 1分あたりの /api と /ws のリクエスト上限(60–100000) | 600 |
security.auth_rate_limit_per_minute | IP ごとの 1分あたりのログイン系リクエスト上限(3–1000。ログイン・2段階認証コード・本人確認・パスワード再設定で共有) | 20 |
security.api_tokens_enabled | API トークンを許可(オープン API を参照) | 無効 |
- ユーザーの手順を増やす機能(2段階認証、パスワード再設定、API トークン)は、スイッチを有効にするまで動作しません。ログイン失敗のロックとレート制限は、最初から上表の既定値で動作します
- 前提条件を満たしていないスイッチは有効にできず、理由がページに表示されます。たとえば「メール」タブで SMTP サーバーとサイトの URL が未設定だとパスワード再設定は有効にできず、デモモードでは 2段階認証、パスワード再設定、API トークンのいずれも有効にできません
- SMTP パスワード、S3 シークレットキー、AI API キーなどの秘密情報は
SECRET_KEYから導出した鍵で暗号化して保存され、API にもページにも再び表示されません - 対応する環境変数(
SMTP_HOST、AI_API_KEYなど)が設定されている項目は環境変数が優先され、ページでは読み取り専用になります。例外はSESSION_TTL_HOURSで、ログイン有効期間の既定値を決めるだけで、項目を固定しません。サーバー起動前に必要な設定(データベース URL、SECRET_KEY、ポートなど)だけは環境変数に置く必要があります - 表示にはメニュー権限
system_settings、保存にはボタン権限system_settings_editが必要です
システム設定の保護
システム設定では、メールサーバー、ファイル保存先、AI API など「データの送り先」を変更できます。乗っ取られた管理者アカウントがこれらを変更すると、パスワード再設定メールを傍受したり、新しいアップロードや AI リクエストを他人のサーバーへ送ったりできてしまいます。そのため次の対策があります。
- 直近の本人確認が必要:設定の保存やテストボタンには、10 分以内のログインまたは本人確認が必要です。それ以外は「本人確認」ダイアログで現在のパスワード(2段階認証を設定済みなら認証コードまたはリカバリーコードも)を求めます。ログイン状態(cookie)を盗んだだけでは設定を変更できず、失敗はログイン失敗のロックに数えます。API トークンの作成や Webhook の追加・変更にも同じ確認が必要です(オープン API を参照)。エンドポイントは
POST /api/admin/reauth、バックエンドではcommon/session.tsのrequireRecentAuth(request)で保護します - 変更通知:保存するたびに、有効なすべてのスーパー管理者へ誰がどの項目を変えたかを通知します(秘密情報は「更新 / クリア」のみ)。操作ログにも記録されます(秘密情報はマスク)
- 予約済みアドレス・内部ネットワーク禁止:SMTP サーバー、S3 エンドポイント、AI API の URL は、クラウドのメタデータ(
169.254.169.254)などの予約済みアドレスを指せません。本番環境では既定で内部ネットワーク(127.0.0.1、10.x、192.168.xなど)も指せません。社内の MinIO やメールサーバーを使う場合はSETTINGS_ALLOW_PRIVATE_NETWORK=trueを設定してください。保存時・テスト時に確認し、AI リクエストは接続時にも実際のアドレスを再確認するため、後からホスト名を内部に向けることもできません。環境変数で固定した値は運用者の判断とみなし、この確認は行いません - 権限は少人数に:表示にはメニュー権限
system_settings、変更にはボタン権限system_settings_editが必要で、既定ではスーパー管理者だけが持っています
本番環境での推奨:
- 「必須にするロール」にスーパー管理者を加え、すべてのスーパー管理者に 2段階認証を使わせる
- 重要な設定は環境変数で固定する。たとえば
APP_BASE_URL(パスワード再設定リンクのサイト URL)やSMTP_HOST。こうすれば管理者アカウントが乗っ取られても変更できません - 普段の作業にはスーパー管理者アカウントを使わず、
system_settings_editは本当に必要な人にだけ付与する
設定項目を追加する
設定項目は apps/api/src/common/settings.ts の SETTING_DEFINITIONS に、グループ・型(真偽値、整数、文字列、秘密情報、列挙、文字列リスト)・既定値・範囲・固定に使う環境変数・公開するかどうか(公開項目は /api/admin/app-info 経由で未ログインのページにも渡されます)・利用できない理由とともに定義します。新しい環境変数名は common/settings-env.ts にも追加します。フロントエンドでは apps/web/src/modules/admin/pages/settings/form.ts の FIELD_META にラベルと説明を追加し、同じディレクトリの index.tsx で該当タブに項目を配置して、そのページの locales/ に翻訳を追加します。コードからは app.settings.get() で読みます。レート制限のように毎リクエスト実行される箇所では、データベースを待たずにキャッシュを返す app.settings.peek() を使います。
新機能を「既定で無効、管理者が有効にできる」ようにしたい場合は、環境変数を増やすのではなく、ここに設定項目を追加してください。
サーバー側セッション
- ログインすると
sessionsテーブルにセッションが作成されます。cookiecastor_session(暗号化、HttpOnly、SameSite=Lax)の中身はセッション ID と CSRF トークンだけで、ログイン状態とユーザーはテーブルの行で決まります - セッションによる
/api/への書き込みリクエスト(POST / PUT / PATCH / DELETE)は、この CSRF トークンをX-CSRF-Tokenヘッダーで送る必要があります。フロントエンドのリクエストクライアントが自動で付けます(バックエンドを参照) - セッションはリクエストごとに 1回確認され、最終操作時刻と有効期限の更新は 1分に 1回までです(スライド延長)
- 期限切れ、または取り消しから 1日以上たったセッションは、スケジューラープロセスが 1時間ごとに削除します
次の操作でセッションは即座に無効になり、次のリクエストは 401 になります。
| 操作 | 対象 |
|---|---|
| ログアウト | 現在のセッション |
| パスワード変更 | 本人の他のセッション(このデバイスはログインしたまま) |
| 管理者によるパスワード変更・無効化・削除 | そのユーザーの全セッション |
| メールのリンクからのパスワード再設定 | そのユーザーの全セッション |
| 強制ログアウト | 指定したセッション |
バックエンドでログイン済みかどうかを判定するときは、cookie の中身を読まず common/session.ts の isSignedIn(request) を使います。
オンラインユーザー
「システム管理 → セキュリティと監査 → オンラインユーザー」には、ログイン中のセッション(ユーザー、デバイス、IP、ログイン日時、最終操作)が操作者のデータ権限で絞り込まれて表示されます。ボタン権限 system_sessions_revoke があれば強制ログアウトできます。ただし自分の現在のセッションは対象外で、スーパー管理者をログアウトさせられるのはスーパー管理者だけです。
各ユーザーは「個人設定 → ログイン中のデバイス」で、自分がどこでログインしているかを確認し、1台ずつ、または他のすべてのデバイスからログアウトできます。
2段階認証
システム設定で有効にすると:
- ユーザーは「個人設定 → 2段階認証」で認証アプリ(Google Authenticator、Microsoft Authenticator、1Password など)で QR コードを読み取り、最初のコードを入力して設定します。このとき一度だけ表示されるリカバリーコードを 10個受け取ります
- 設定済みのユーザーは、ログイン時にパスワードの後で 6桁のコード、または代わりにリカバリーコードを入力します
- 「必須にするロール」に選ばれたロールのメンバーで未設定の人は、ログイン時にまず設定を求められ、完了するとそのままログインします。これらのユーザーは自分で無効にできません
- スマートフォンとリカバリーコードの両方を失ったユーザーは、管理者が「ユーザー管理 → ユーザー編集」ダイアログの左下からリセットします(
system_users_editが必要。スーパー管理者をリセットできるのはスーパー管理者だけ)
詳細:
- TOTP(6桁、30秒、SHA-1)で、前後 1期間分の時計のずれを許容します。同じ期間のコードは 1回しか使えません
- 秘密鍵は
SECRET_KEYから導出した鍵で AES-256-GCM 暗号化して保存します。SECRET_KEYを変えると既存の設定は無効になります - リカバリーコードは sha256 ハッシュだけを保存し、それぞれ 1回だけ使えます
- コードの入力ミスはパスワードの入力ミスと同様にログイン失敗のロックに数えます
- パスワードは正しいが 2段階目がまだのセッションは待機状態で、ログインが必要な API には一切アクセスできず、5分で失効します。2段階目を通過すると新しいセッション ID が発行され、「ログイン成功」のログと最終ログイン日時もこの時点で記録されます
- 全体スイッチを無効にしてもログイン時にコードを求めなくなるだけで、設定は保持され、再び有効にするとすぐに元に戻ります
パスワード再設定
先にシステム設定の「メール」タブで SMTP サーバーとサイトの URL を入力し(「テストメールを送信」で確認できます。設定を参照)、「セキュリティ」タブで有効にします。ログイン画面に「パスワードをお忘れですか?」が表示されます。
- ユーザーはアカウントに登録したメールアドレスを入力します。アドレスが存在するかどうかにかかわらず同じメッセージが表示され、アカウントの有無は漏れません
- リンクは
<サイトの URL>/reset-password?token=…の形式で、有効期限は 30分、1回だけ使えます。再度リクエストすると以前のリンクは無効になります - 新しいパスワードはパスワードのルールでチェックされ、再設定後はそのユーザーのすべてのデバイスがログアウトします。2段階認証を設定したアカウントは、次回ログイン時にもコードが必要です
メールの言語はユーザーの現在の表示言語に合わせます。ローカル開発では MAIL_DRIVER=log にすると、メールは送信されずバックエンドのログに出力されます。
パスワードのルール
パスワードを設定する箇所ではすべて、システム設定のルールでチェックします(パスワード変更、ユーザーの作成・編集・インポート、パスワード再設定)。既存のパスワードには影響しません。フロントエンドのフォームも app-info から同じルールを読み、送信前に案内します。
レート制限
- すべての
/apiと/wsリクエストを IP ごとに数え、1分あたりの上限を超えると 429 を返します。エラーは翻訳され、Retry-Afterヘッダーが付きます。静的ファイルと/healthは数えません - ログイン、2段階認証コード(ログイン時と設定時)、本人確認、パスワード再設定(メールの申請と新しいパスワードの設定)は、より厳しい上限
security.auth_rate_limit_per_minuteを共有します - カウントはプロセスのメモリにあるため、複数インスタンスではインスタンスごとに数えます。実際の上限は最大で設定値 × インスタンス数になります
RATE_LIMIT_ENABLED=falseで全体を無効にできます(テスト環境では無効)
レート制限とログイン失敗のロックは同時に働きます。前者はリクエストの頻度を、後者は失敗できる回数を制限します。
ログイン失敗のロック
- 失敗として数えるのは、パスワードの誤り(無効化されたアカウントで正しいパスワードを入力した場合を含む)、ログイン時の 2段階認証コードやリカバリーコードの誤り、本人確認の失敗(パスワードまたはコードの誤り)です。直近
security.login_lockout_minutes分間の回数を、IP ごととユーザー名ごとに数えます - どちらかが
security.login_max_failuresに達すると、ログイン、2段階目、本人確認はいずれも 429 を返し、古い失敗が期間外に出るまで続きます。デモモードでは IP だけを数えるため、共有のデモアカウントを故意にロックすることはできません - ログインに成功すると、そのユーザー名とその IP のそれ以前の失敗は数えられなくなります。記録はログイン履歴に残ります
LOGIN_MAX_FAILURES/LOGIN_LOCKOUT_MINUTESで両方の値を固定できます。設定を参照してください
