コンテンツにスキップ

0004. login_id 方式の認証 (合成メールへの決定的変換)

コンテキストと課題

処理業者のメンバー (ドライバー・一般スタッフ) の多くは、業務で使える個人のメールアドレスを 持たない。一方で Supabase Auth は メール + パスワード を前提とする。「メールを持たない社員でも ログインできる」ようにしつつ、Supabase Auth をそのまま使いたい。

決定

ログインは login_id で行い、内部で決定的に合成メールへ変換して Supabase Auth に渡す

// apps/web/src/lib/auth-login-id.ts
export const LOGIN_EMAIL_DOMAIN = 'members.waste-link.local';
export function loginIdToEmail(identifier: string): string {
  const v = identifier.trim();
  if (v.includes('@')) return v;                         // すでにメールならそのまま
  return `${v.toLowerCase()}@${LOGIN_EMAIL_DOMAIN}`;     // login_id → 合成メール
}
  • 例: yamadayamada@members.waste-link.local@ を含む入力は実メールとみなし素通し。
  • 同じ変換規則を 3 箇所で一致 させる: Web のログイン (AuthContext)、API のメンバー作成 (team.tssyntheticEmail)、公開サインアップ (signup.tssignupLoginIdToEmail)。 login_id の形式は ^[a-z0-9][a-z0-9._-]{2,}$ (英小文字/数字始まり・._- 可・3 文字以上)。
  • DB 側は profiles.login_id 列 (全体で一意・部分インデックス) を追加し (migration 32)、 メール非保有メンバーを許容するため profiles.email を nullable 化 (migration 35)。
  • login_id の衝突は合成メールの一意制約で検出する (重複 = 422 を「login_id の衝突」として扱う)。

影響 (Consequences)

良い点

  • 実メールを持たない社員でも login_id + パスワード でログインでき、Supabase Auth の メール+パスワード機構を改造せず流用できる。
  • 変換が 決定的 なので、管理者は login_id だけを管理すればよく、ユーザーにメールを意識させない。
  • テストアカウント (admin1 / driver1 など) も同じ仕組みで *@members.waste-link.local に マップされ、ローカル/開発で一貫してログインできる。

トレードオフ / 注意点

  • *.members.waste-link.local非ルータブルな .local ドメイン で実メールは届かない。 そのため login_id ユーザーには パスワードリセットのメール導線が無い (管理者によるリセット運用が前提)。
  • 変換規則が Web と API の 複数箇所に重複している。片方だけ変えると login_id 衝突判定や ログインが壊れるため、変更時は全箇所を揃える必要がある (各ファイルにその旨のコメントあり)。
  • 実メールを持つユーザー (@ を含む入力) と login_id ユーザーが混在し得る。loginIdToEmail@ 素通しがその両対応を担う。

根拠 (典拠)

  • apps/web/src/lib/auth-login-id.ts:1-6LOGIN_EMAIL_DOMAIN = 'members.waste-link.local'loginIdToEmail
  • apps/web/src/contexts/AuthContext.tsx:1import { loginIdToEmail }(ログインで使用)。
  • apps/api/src/routes/team.ts:9-11,99-100,109LOGIN_EMAIL_DOMAIN / LOGIN_ID_RE / syntheticEmail = ${loginId}@${LOGIN_EMAIL_DOMAIN} / 422 を login_id 衝突として扱うコメント。
  • apps/api/src/routes/signup.ts:44-57LOGIN_EMAIL_DOMAIN / SIGNUP_LOGIN_ID_RE = /^[a-z0-9][a-z0-9._-]{2,}$/ / signupLoginIdToEmail
  • supabase/migrations/00000000000032_profiles_login_id.sql:1-4login_id 列追加 + where login_id is not null の部分一意インデックス。
  • supabase/migrations/00000000000035_profiles_email_nullable.sql:1-2email を nullable 化。
  • README.md:164-166,225 — login_id ログインと決定的変換、.local は非ルータブル (実メール不達) の注記。