オペレーター登録・Passkey登録
オペレーター登録・Passkey登録
Section titled “オペレーター登録・Passkey登録”Document ID: SRS-TEN-002 Parent: SRS-ROOT-001 v0.5 Version: 0.3 Status: Draft Last Updated: 2026-05-05 Depends on: SRS-TEN-001 v0.4 依存される: SRS-TEN-003, SRS-MST-001, SRS-RES-001/002/003/004/005, SRS-CUS-001/002/004, SRS-REG-001/002, SRS-WRK-001
本書は SRS-ROOT-001 v0.5 に従う。
Phase 1 で x-dev-* ヘッダ前提の apps/api/src/middleware/dev-auth.ts を廃止し、オペレーター認証を better-auth + Passkey 必須 + TOTP 任意 + recovery code + サーバーサイド session に置き換える。
本SRSの責務は「認証主体の生成・本人確認・認証要素登録・セッション管理・招待トークン受領」までである。「店舗所属リンクとロール変更」は SRS-TEN-003 が、「operator → staff の業務リンク」は SRS-CUS-004 が所有する。
2. ユーザーストーリー
Section titled “2. ユーザーストーリー”- As a 招待された新規オペレーター, I want to メール招待からそのまま Passkey 登録して初回ログインしたい, so that パスワード無しで即運用開始できる
- As a 既存オペレーター, I want to 新しい端末に Passkey を追加登録したい, so that 端末紛失時も継続運用できる
- As a 既存オペレーター, I want to TOTP を任意で有効化したい, so that 高リスク操作時の保険を持てる
- As a 既存オペレーター, I want to Passkey を全部失った時に recovery code とメール確認で復旧したい, so that オーナー依存の手動復旧を避けられる
- As a 管理者, I want to オペレーター一覧と招待状態を見たい, so that 誰がまだ未受諾か把握できる
3. ユースケース
Section titled “3. ユースケース”3.1 主シナリオ(新規オペレーターの招待受諾と Passkey 登録)
Section titled “3.1 主シナリオ(新規オペレーターの招待受諾と Passkey 登録)”- 管理者が
POST /api/admin/operators/invitationsでメール招待を作成する(SRS-TEN-003 の責務だが、実装は本SRSの招待トークン仕様に従う) - システムが
operator_invitationに 1 行作成し、32 byte ランダムトークンをメール送信する - 招待先ユーザーがリンクを開く
- システムがトークンハッシュ一致・未失効・未取消・未受諾を検証する
operator.emailが未存在なら新規operatorを作成する- ブラウザが Passkey registration challenge を取得する
- ユーザーが OS / ブラウザの WebAuthn UI で Passkey を登録する
- システムが
passkey_credentialを保存する - ユーザーが希望すれば TOTP を有効化する。不要なら skip できる
- システムが recovery code 10 個を生成し、この場で一度だけ表示する
- システムが
operator_sessionを作成し、active_store_id = invitation.store_idをセットする - SRS-TEN-003 の受諾処理と同一トランザクションで
operator_store_linkを作成する - ユーザーは
/api/admin/*を利用可能になる
3.2 代替フロー
Section titled “3.2 代替フロー”- AF-1 既存オペレーターが別店舗招待を受ける: 新規
operatorは作らず、既存ログイン済みセッションで本人確認済みの同一メールオペレーターに紐付ける - AF-2 既存オペレーターが新端末で Passkey を追加: 既存ログイン済みセッションから追加登録し、最後の 1 本を削除する前に代替 Passkey の存在を要求する
- AF-3 TOTP を使わない: Passkey のみで通常ログイン可能。TOTP 有効ユーザーのみ 2 段階目を要求する
- AF-4 recovery code 再生成: 旧 10 個は全失効し、新 10 個をその場で再表示する
- AF-5 全端末ログアウト: 現在セッション以外を一括失効する API を提供する。UI は Phase 2
3.3 例外フロー
Section titled “3.3 例外フロー”- EF-1 招待トークン不正:
AUTH.INVITATION_INVALID404 - EF-2 招待トークン失効:
AUTH.INVITATION_EXPIRED410 - EF-3 招待トークン取消済み:
AUTH.INVITATION_REVOKED410 - EF-4 受諾済みトークン再使用: 既に同一 operator/store link が成立していれば 200 no-op、そうでなければ 409
- EF-5 既存オペレーター受諾でログイン中ユーザーのメールが招待メールと不一致:
AUTH.INVITATION_EMAIL_MISMATCH409 - EF-6 Passkey 登録に成功したが
operator_store_link作成に失敗: トランザクション全体 rollback - EF-7 recovery code は正しいが email confirmation が無い:
AUTH.RECOVERY_EMAIL_CONFIRMATION_REQUIRED403 - EF-8
POST /api/auth/sign-in/email: 常に 404 - EF-9 レート制限超過: 429
AUTH.RATE_LIMITED
4. UI仕様
Section titled “4. UI仕様”4.1 画面構成
Section titled “4.1 画面構成”- 招待受諾画面
- Passkey 登録画面
- TOTP 任意設定画面
- recovery code 表示画面
- アカウントセキュリティ画面
- 自分のセッション管理画面(最小)
4.2 要素と挙動
Section titled “4.2 要素と挙動”- 招待受諾画面は token 検証後に Passkey 登録を開始する
- Passkey 登録は OS ネイティブ UI を呼ぶだけで、パスワード入力欄を持たない
- TOTP 設定画面は QR と確認コード入力を持つ
- recovery code 画面は「後で再表示不可」を明示する
- セキュリティ画面は登録済み Passkey 一覧、TOTP 有効/無効、recovery code 再生成、全端末ログアウトを持つ
4.3 バリデーション
Section titled “4.3 バリデーション”- email は lower-case normalize 後に
citext一意 - 招待 token は base64url 43 文字
- TOTP code は 6 桁
- recovery code は英大文字・数字 8〜12 桁表示、DB には hash のみ保存
- Passkey nickname は任意 1〜50 文字
5. API仕様
Section titled “5. API仕様”5.1 エンドポイント一覧
Section titled “5.1 エンドポイント一覧”| Method | Path | 用途 | 認証 | permission |
|---|---|---|---|---|
| GET | /api/admin/operators | 店舗所属オペレーター一覧 + 招待状態 | operator session | admin:operator:read |
| POST | /api/admin/operators/invitations | 招待作成 | operator session | admin:operator:create |
| POST | /api/admin/operators/invitations/:id/revoke | 招待取消 | operator session | admin:operator:create |
| POST | /api/auth/invitations/accept | 招待受諾開始 | token | なし |
| POST | /api/auth/passkeys/register/options | Passkey 登録 challenge 発行 | token or session | なし |
| POST | /api/auth/passkeys/register/verify | Passkey 登録完了 | token or session | なし |
| POST | /api/auth/passkeys/authenticate/options | Passkey 認証 challenge 発行 | public | なし |
| POST | /api/auth/passkeys/authenticate/verify | Passkey 認証完了 | public | なし |
| POST | /api/auth/totp/setup | TOTP 初期化 | session | なし |
| POST | /api/auth/totp/verify | TOTP 有効化 / 認証 | pending session | なし |
| POST | /api/auth/recovery-codes/regenerate | recovery code 再発行 | session | なし |
| POST | /api/auth/recovery/sign-in | recovery code + email confirmation 復旧 | public | なし |
| POST | /api/auth/sessions/logout | 現在セッション logout | session | なし |
| POST | /api/auth/sessions/logout-all | 他端末 session 全失効 | session | なし |
| POST | /api/auth/stores/switch | active store 切替 | session | なし |
| GET | /api/admin/auth/me | 自分の session / factor 状態 | session | なし |
5.2 リクエスト / レスポンス要約
Section titled “5.2 リクエスト / レスポンス要約”POST /api/admin/operators/invitations
- request:
{ email, role_id } - response:
{ data: { invitation_id, expires_at, email, role_id } }
POST /api/auth/invitations/accept
- request:
{ token } - response:
{ data: { invitation_id, email, store_id, existing_operator: boolean } }
POST /api/auth/recovery/sign-in
- request:
{ email, recovery_code, email_confirmation_token, new_passkey_attestation } - response:
{ data: { session_id, active_store_id, recovered: true } }
POST /api/auth/stores/switch
- request:
{ store_id } - response:
{ data: { active_store_id } }
5.3 エラーコード
Section titled “5.3 エラーコード”| code | 意味 |
|---|---|
AUTH.UNAUTHENTICATED | 未ログイン |
AUTH.FORBIDDEN | 権限不足 |
AUTH.INVITATION_INVALID | token 不正 |
AUTH.INVITATION_EXPIRED | token 失効 |
AUTH.INVITATION_REVOKED | token 取消済み |
AUTH.INVITATION_EMAIL_MISMATCH | 既存アカウントの email 不一致 |
AUTH.PASSKEY_REQUIRED | Passkey 未登録 |
AUTH.TOTP_REQUIRED | TOTP 必須だが未通過 |
AUTH.RECOVERY_CODE_INVALID | recovery code 不正 |
AUTH.RECOVERY_CODE_USED | recovery code 使用済み |
AUTH.RECOVERY_EMAIL_CONFIRMATION_REQUIRED | email confirmation 未完了 |
AUTH.STORE_NOT_LINKED | active store 切替先に所属なし |
AUTH.RATE_LIMITED | レート制限 |
6. データモデル影響
Section titled “6. データモデル影響”6.1 スキーマ
Section titled “6.1 スキーマ”operator(共通 auth 主体、UUIDv7、store_id なし、親 §7.1.1 例外)
Section titled “operator(共通 auth 主体、UUIDv7、store_id なし、親 §7.1.1 例外)”| カラム | 型 | 制約 |
|---|---|---|
id | uuid | PK |
email | citext | UNIQUE, NOT NULL |
name | varchar(100) | NOT NULL |
status | varchar(20) | NOT NULL DEFAULT active |
last_login_at | timestamptz | NULL |
created_at | timestamptz | NOT NULL |
updated_at | timestamptz | NOT NULL |
passkey_credential(store_id なし)
Section titled “passkey_credential(store_id なし)”| カラム | 型 | 制約 |
|---|---|---|
id | uuid | PK |
operator_id | uuid | FK → operator(id) |
credential_id | bytea | UNIQUE, NOT NULL |
public_key | bytea | NOT NULL |
sign_count | bigint | NOT NULL DEFAULT 0 |
transports | jsonb | NOT NULL DEFAULT [] |
aaguid | uuid | NULL |
nickname | varchar(50) | NULL |
last_used_at | timestamptz | NULL |
revoked_at | timestamptz | NULL |
created_at | timestamptz | NOT NULL |
totp_secret(store_id なし)
Section titled “totp_secret(store_id なし)”| カラム | 型 | 制約 |
|---|---|---|
operator_id | uuid | PK, FK |
secret_encrypted | bytea | NOT NULL |
enabled_at | timestamptz | NULL |
created_at | timestamptz | NOT NULL |
updated_at | timestamptz | NOT NULL |
recovery_code(store_id なし)
Section titled “recovery_code(store_id なし)”| カラム | 型 | 制約 |
|---|---|---|
operator_id | uuid | FK |
slot | smallint | 1..10 |
code_hash | bytea | NOT NULL |
used_at | timestamptz | NULL |
batch_id | uuid | NOT NULL |
created_at | timestamptz | NOT NULL |
| PK | (operator_id, slot) |
operator_session(store_id なし、server-side session)
Section titled “operator_session(store_id なし、server-side session)”| カラム | 型 | 制約 |
|---|---|---|
id | uuid | PK |
operator_id | uuid | FK → operator(id) |
token_hash | bytea | UNIQUE, NOT NULL |
active_store_id | uuid | NULL |
ip | inet | NULL |
user_agent | text | NULL |
expires_at | timestamptz | NOT NULL |
last_seen_at | timestamptz | NOT NULL |
revoked_at | timestamptz | NULL |
created_at | timestamptz | NOT NULL |
operator_invitation(store-scoped)
Section titled “operator_invitation(store-scoped)”| カラム | 型 | 制約 |
|---|---|---|
id | uuid | PK |
store_id | uuid | FK → store(id), NOT NULL |
email | citext | NOT NULL |
role_id | uuid | FK → role(store_id, id), NOT NULL |
token_hash | bytea | UNIQUE, NOT NULL |
invited_by_operator_id | uuid | FK → operator(id), NOT NULL |
expires_at | timestamptz | NOT NULL |
accepted_at | timestamptz | NULL |
accepted_operator_id | uuid | FK → operator(id) |
revoked_at | timestamptz | NULL |
created_at | timestamptz | NOT NULL |
operator_action_log への変更
Section titled “operator_action_log への変更”actor_kind enum('operator','system') 列を追加し、operator_id に条件付き CHECK 制約を入れる。既存行は operator_id IS NULL なら 'system'、それ以外は 'operator' で埋める。
FK 制約は 0012 では張らない。理由は dev-auth 移行 8 PR §7.9 の順序で、PR-3 (本 migration) 〜 PR-6 までは dev-auth.ts が動作しており、x-dev-operator-id ヘッダ由来の UUID(operator テーブルに不在)が operator_action_log に書き込まれ続ける。FK を 0012 で張ると、既存 dev 行と以降の dev 期間の audit INSERT が同時に壊れる。よって FK は dev-auth 物理削除 (PR-7) 直後の後続 migration(番号は実装時に確保)で追加する。
CREATE TYPE operator_action_actor_kind AS ENUM ('operator','system');
ALTER TABLE operator_action_log ADD COLUMN actor_kind operator_action_actor_kind NOT NULL DEFAULT 'system';
UPDATE operator_action_log SET actor_kind = CASE WHEN operator_id IS NULL THEN 'system'::operator_action_actor_kind ELSE 'operator'::operator_action_actor_kind END;
ALTER TABLE operator_action_log ADD CONSTRAINT operator_action_log_actor_kind_chk CHECK ( (actor_kind = 'operator' AND operator_id IS NOT NULL) OR (actor_kind = 'system' AND operator_id IS NULL) );
-- FK は dev-auth 物理削除後の後続 migration で追加する。0012 では張らない。-- ALTER TABLE operator_action_log-- ADD CONSTRAINT operator_action_log_operator_fk-- FOREIGN KEY (operator_id) REFERENCES operator(id) ON DELETE RESTRICT;ローカル開発 DB に既存の dev データがあるケースでは、本 migration 適用前に docker compose down -v && docker compose up -d db && bun run db:bootstrap && bun run db:migrate && bun run db:seed で再構築するか、actor_kind backfill だけ行う本 migration はそのまま適用可能。
6.2 マイグレーション計画
Section titled “6.2 マイグレーション計画”0012_operator_auth_and_sessions.sql で以下を 1 ファイルに統合する。
operator,passkey_credential,totp_secret,recovery_code,operator_session,operator_invitationの作成operator_action_log.actor_kind列追加とoperator_id条件付き制約・FK 整備operator_invitationに対する RLS / GRANT- auth 系 store-less テーブルの GRANT(RLS なし、
appロールに SELECT/INSERT/UPDATE のみ)
permission backfill (admin:operator:*) は 0013_operator_membership_and_staff_link.sql で SRS-TEN-003 と統合実施する。
6.3 RLS / GRANT 方針
Section titled “6.3 RLS / GRANT 方針”operator_invitationはstore_idを持つため通常の tenant RLS を貼るoperator,passkey_credential,totp_secret,recovery_code,operator_sessionは 共通 auth エンティティ例外(親 §7.1.1)。store_idを持たない- 上記 store-less auth テーブルは tenant RLS の安全網を持ちにくいため、アクセス主体を
packages/authに閉じ込める /api/admin/*の permission 判定は app 層、/api/auth/*の本人確認は better-auth に集約するemail/passwordroute は middleware で 404 に落とす(二重ガード)
7. 業務ルール
Section titled “7. 業務ルール”7.1 Passkey 登録
Section titled “7.1 Passkey 登録”- admin 利用には少なくとも 1 本の active Passkey が必要
- 招待受諾で新規オペレーターを作る場合、Passkey 登録完了前に session を発行しない
- 既存ログイン中セッションから追加登録は許可する
- 最後の active Passkey を削除する操作は、同一トランザクションで代替 Passkey が追加されない限り拒否する
7.2 Passkey 再登録
Section titled “7.2 Passkey 再登録”- 端末買替時は「追加登録 → 旧 credential revoke」の順のみ許可する
revoked_atを持つだけで物理削除しない- 監査ログ
auth.passkey.add/auth.passkey.revokeを記録する
7.3 紛失リカバリ(親 §6.3 準拠)
Section titled “7.3 紛失リカバリ(親 §6.3 準拠)”- 復旧条件は unused recovery code 1 個 + email confirmation 完了
- email-only recovery は禁止
- recovery 成功時は、旧 session を全失効し、新しい Passkey を同一フローで必須登録する
- recovery code 使用時は
used_atを即時更新する - recovery 完了後は新 10 個へ再生成する
7.4 TOTP 任意化
Section titled “7.4 TOTP 任意化”- TOTP は opt-in
- 有効化済みユーザーは Passkey 認証後に TOTP を追加で要求する
- TOTP secret は暗号化保存し、平文再表示しない
- TOTP 無効化はログイン済み + recovery code または TOTP 現値のどちらかを要求する
7.5 recovery code 運用
Section titled “7.5 recovery code 運用”- 常に 10 個
- DB には hash のみ保存
- 表示は生成直後 1 回のみ
- 再生成時に旧 batch を全失効する
- 10 個全消費時は recovery フロー不可。本人ログイン済み時の再生成が必要
7.6 招待トークン
Section titled “7.6 招待トークン”- raw token は 32 byte cryptographic random
- 表示・URL 埋込は base64url
- DB には SHA-256 hash のみ保存
- 有効期限は 72h 固定
- revoke 後の再送は旧 row 再利用でなく新 row 発行を推奨する。履歴が明確だから
- 既存 operator の別店舗招待受諾は「同一 email かつ本人ログイン済み」を必須にする
7.7 active_store_id
Section titled “7.7 active_store_id”- session は
active_store_idを持つ - 認可は毎 request で
active_store_id + operator_store_link + role_permissionから解決する - role 変更・link revoke は次 request から即反映する
- store switch は switch 先 link 存在をサーバー側で再検証する
7.8 better-auth 採用方針
Section titled “7.8 better-auth 採用方針”emailAndPassword.enabled = false(パスワード認証を完全 OFF)- passkey plugin、two-factor (TOTP) plugin を採用
- organization plugin は 不採用。マルチテナントは自前
operator_store_linkで行う - session の
additionalFieldsにactive_store_idを生やす generateId: () => uuidv7()を全 better-auth ID で統一- Hono integration は公式採用、
/api/auth/*にマウント - cookie は
HttpOnly + Secure + SameSite=Lax、状態変更系はX-Requested-With必須(CSRF 二重防衛)
7.9 dev-auth 移行手順
Section titled “7.9 dev-auth 移行手順”- PR-1: 仕様凍結(parent v0.5、TEN-001 v0.4、TEN-002 v0.2、TEN-003 v0.2、CUS-004 v0.1 同時承認)
- PR-2: auth 抽象化先行 —
apps/apiからDevAuthContext直参照を剥がし、auth.bypassForTest()と共通AuthContextをpackages/authに作る。挙動は dev-auth のまま - PR-3: DB 基盤 migration —
0012,0013で auth 系・link 系テーブルを追加。permission backfill も0013で実施 - PR-4: better-auth 実装 —
packages/authに better-auth 設定、Passkey/TOTP/recovery code/session 管理、/api/auth/*mount を実装 - PR-5: admin auth 切替 —
/api/admin/*をdevAuthから better-auth session middleware へ差し替える - PR-6: テスト移行 —
apps/api/test/*のx-dev-*ヘッダ依存をauth.bypassForTest()へ置き換え - PR-7: 物理削除と CI ガード —
apps/api/src/middleware/dev-auth.tsを削除し、本番 build / grep CI でx-dev-混入を落とす - PR-8: seed / setup 更新 — setup guide, seed, test fixtures を better-auth 前提へ更新
7.10 layer 分離(親 §7.7.3)
Section titled “7.10 layer 分離(親 §7.7.3)”packages/authPERMISSIONS定数PRESET_ROLE_PERMISSIONSresolveReservationTransitionPermission(input)等の純関数 resolverauth.bypassForTest()ヘルパ
apps/api/src/middlewarerequirePermission(key, opts?)requireReservationTransitionPermission(body の to + reopen から resolver を呼ぶ)
packages/auth に Hono を import しない。
8. 非機能要件
Section titled “8. 非機能要件”- 認証開始 API p95 300ms 以下
- Passkey verify p95 500ms 以下
- 招待メール送信は 30 秒以内に job 開始
- Rate limit は passkey auth 10 回/分、TOTP 5 回/15 分、recovery 5 回/15 分
- 監査ログは 1 年保持
- 本番 build に
dev-auth.tsを含めない(CI で grep 検証)
9. セキュリティ・認可
Section titled “9. セキュリティ・認可”9.1 使用する permission キー
Section titled “9.1 使用する permission キー”| key | 用途 | 初期付与 |
|---|---|---|
admin:operator:read | オペレーター一覧・招待状態の閲覧 | owner / manager |
admin:operator:create | 招待作成・取消 | owner / manager |
admin:operator:update | オペレーター更新 | owner / manager |
admin:operator:retire | オペレーター無効化・復帰 | owner / manager |
9.2 追加セキュリティ規律
Section titled “9.2 追加セキュリティ規律”emailAndPassword.enabled = false/api/auth/sign-in/emailは 404- Cookie は
HttpOnly + Secure + SameSite=Lax - 状態変更系は
X-Requested-With必須 auth.bypassForTest()を提供し、x-dev-*ヘッダは削除する- 招待 token, session token, recovery code は raw 値を DB 保存しない
10. 受け入れ基準(Given-When-Then)
Section titled “10. 受け入れ基準(Given-When-Then)”- GWT-1 新規招待受諾成功: Given 未登録 email の有効招待 / When Passkey 登録を完了 / Then
operator,passkey_credential,operator_session,operator_store_linkが作成される - GWT-2 既存オペレーター別店舗招待: Given 既存 operator が別店舗招待を受領 / When 同一 email の既存 session で受諾 / Then 新規
operatorは作られず link のみ増える - GWT-3 招待 token 不正: Given 無効 token / When 受諾開始 / Then 404
AUTH.INVITATION_INVALID - GWT-4 招待 token 失効: Given
expires_at < now()/ When 受諾開始 / Then 410AUTH.INVITATION_EXPIRED - GWT-5 招待 token 取消済み: Given
revoked_at IS NOT NULL/ When 受諾開始 / Then 410AUTH.INVITATION_REVOKED - GWT-6 Passkey 必須: Given 新規招待受諾中 / When Passkey verify 前に admin session 要求 / Then 401
- GWT-7 TOTP 任意: Given TOTP 未設定 operator / When Passkey sign-in / Then 追加 factor 無しで session 作成
- GWT-8 TOTP 必須化: Given TOTP 設定済み operator / When Passkey sign-in 成功 / Then pending session となり TOTP verify 前は admin API 利用不可
- GWT-9 recovery code 10 個発行: Given 初回登録完了 / When recovery code 表示 / Then 10 個のみ返り、再取得 API は存在しない
- GWT-10 recovery code 使用: Given 未使用 code + email confirmation 成功 / When recovery sign-in / Then code が消費され旧 session 全失効、新 Passkey 必須登録
- GWT-11 recovery code 再利用拒否: Given
used_at IS NOT NULL/ When 同じ code を送信 / Then 403AUTH.RECOVERY_CODE_USED - GWT-12 recovery code 再生成: Given ログイン済み operator / When regenerate / Then 旧 batch 全失効、新 batch 10 件作成
- GWT-13 email/password 404: Given 任意 / When
POST /api/auth/sign-in/email/ Then 404 - GWT-14 active store switch: Given 2 店舗 link を持つ operator / When store B に switch / Then session.active_store_id = B
- GWT-15 未所属 store switch 拒否: Given store B link 無し / When switch / Then 403
AUTH.STORE_NOT_LINKED - GWT-16 全端末 logout: Given 複数 session / When logout-all / Then current 以外が失効
- GWT-17 招待一覧閲覧: Given owner session / When
/api/admin/operators/ Then store 内 operator と pending invitation を見られる - GWT-18 招待権限不足: Given staff session / When
POST /api/admin/operators/invitations/ Then 403 - GWT-19 既存 email 不一致拒否: Given 招待 email と別 email の login session / When accept / Then 409
AUTH.INVITATION_EMAIL_MISMATCH - GWT-20 actor_kind CHECK: Given
actor_kind='operator'でoperator_id IS NULLを INSERT / Then DB CHECK 違反 - GWT-21 dev-auth 排除: Given 本番 build / When
dev-auth.ts/x-dev-*の存在 grep / Then 0 件
11. テスト計画
Section titled “11. テスト計画”- Unit: token hash, recovery code batch rotation, active store switch validation, TOTP required state machine
- Integration: invite accept, passkey register, recovery, session revoke, rate limit, 404 email route
- Contract:
/api/auth/*と/api/admin/operators*の schema 固定 - Security: raw token 非保存、revoked session 再利用不能、cookie 属性、CSRF header
- Cross-SRS:
operator_store_link作成は SRS-TEN-003 と同時検証
12. 関連ジョブ(graphile-worker)
Section titled “12. 関連ジョブ(graphile-worker)”send-operator-invitation-emailcleanup-expired-operator-sessionscleanup-expired-operator-invitations
ジョブ命名・enqueue 規約は SRS-WRK-001 に従う。
13. Open Questions
Section titled “13. Open Questions”| # | 内容 | 扱い |
|---|---|---|
| OQ-TEN-002-01 | 共有端末 (受付 iPad) モード | Phase 2 持越し(親 OQ-15) |
| OQ-TEN-002-02 | better-auth v1.x の継続採用判断(Bun 互換) | Phase 1 後半に再評価 |
| OQ-TEN-002-03 | recovery code 全消費後の管理者再招待フロー | Phase 2 検討 |
14. 変更履歴
Section titled “14. 変更履歴”| Version | Date | Author | Change |
|---|---|---|---|
| 0.1 | 2026-05-05 | Codex / yudai | 初版ドラフト |
| 0.2 | 2026-05-05 | yudai (with Codex co-design) | Round 2 反映: migration 番号 0012 に確定、actor_kind enum 採用、layer 分離 (packages/auth = resolver, apps/api = middleware)、permission 47 表整合 (admin:operator:* 4 keys)、Smart Pay は完全 Phase 2 へ。dev-auth 移行 8 PR 手順を §7.9 に明記 |
| 0.3 | 2026-05-05 | yudai (with Codex co-design) | §6.1 operator_action_log 変更: 0012 では FK を張らない方針を明文化(dev-auth 期間中の audit INSERT を壊さないため)。FK 追加は PR-7 dev-auth 物理削除直後の後続 migration へ遅延 |