読み込み中
豆知識
文書バージョン: 1.0 作成日: 2026-01-31 現在の位置づけ: Proposed 優先度: HIGH (Security-Critical for B2B Accounting Software)
This specification outlines the backend requirements for implementing industry-standard account recovery features. These enhancements address a critical security gap: if a user loses access to their Google account, they currently lose all access to their accounting data.
| Risk | Impact | Mitigation |
|---|---|---|
| User loses Google account | Complete data loss - Cannot access accounting records | Backup email + Recovery codes |
| Google OAuth token revoked | Session invalidated, no recovery path | Multiple recovery methods |
| Account takeover via Google | Single point of failure | MFA options, session management |
| Platform | Recovery Methods |
|---|---|
| GitHub | Backup email, Recovery codes, SSH keys |
| Stripe | Backup email, Recovery codes, Team admins |
| AWS | Recovery codes, MFA devices, Root email |
| Notion | Backup email, Magic links, SSO fallback |
Purpose: Alternative email address for account recovery if primary Google account is inaccessible.
-- Add to users table or create new table
ALTER TABLE users ADD COLUMN backup_email VARCHAR(255) NULL;
ALTER TABLE users ADD COLUMN backup_email_verified BOOLEAN DEFAULT FALSE;
ALTER TABLE users ADD COLUMN backup_email_verified_at TIMESTAMP NULL;
-- Or separate table for audit trail
CREATE TABLE user_backup_emails (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
email VARCHAR(255) NOT NULL,
is_verified BOOLEAN DEFAULT FALSE,
verified_at TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
UNIQUE(user_id)
);
CREATE INDEX idx_backup_emails_user ON user_backup_emails(user_id);
CREATE INDEX idx_backup_emails_email ON user_backup_emails(email);
POST /api/auth/backup-email
GET /api/auth/backup-email
DELETE /api/auth/backup-email
POST /api/auth/backup-email/verify
POST /api/auth/backup-email/resend-verification
Request:
{
"email": "backup@example.com"
}
Response (201):
{
"success": true,
"message": "確認メールを送信しました",
"data": {
"email": "ba***@example.com",
"isVerified": false,
"verificationSentAt": "2026-01-31T10:00:00Z"
}
}
Validation Rules:
Request:
{
"token": "verification-token-from-email"
}
Response (200):
{
"success": true,
"message": "バックアップメールを確認しました",
"data": {
"email": "ba***@example.com",
"isVerified": true,
"verifiedAt": "2026-01-31T10:30:00Z"
}
}
Purpose: 8 single-use codes for emergency account access when all other methods fail.
CREATE TABLE user_recovery_codes (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
code_hash VARCHAR(255) NOT NULL, -- bcrypt hash of code
code_index SMALLINT NOT NULL, -- 1-8 for display order
is_used BOOLEAN DEFAULT FALSE,
used_at TIMESTAMP NULL,
used_ip VARCHAR(45) NULL,
created_at TIMESTAMP DEFAULT NOW(),
expires_at TIMESTAMP NULL, -- Optional: codes can expire
UNIQUE(user_id, code_index)
);
CREATE INDEX idx_recovery_codes_user ON user_recovery_codes(user_id);
-- Track code generation events for audit
CREATE TABLE user_recovery_code_events (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
event_type VARCHAR(50) NOT NULL, -- 'generated', 'used', 'regenerated'
ip_address VARCHAR(45),
user_agent TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
POST /api/auth/recovery-codes/generate
GET /api/auth/recovery-codes/status
POST /api/auth/recovery-codes/verify
DELETE /api/auth/recovery-codes
Purpose: Generate new set of 8 recovery codes. Invalidates any existing codes.
Request:
{
"confirmRegenerate": true // Required if codes already exist
}
Response (201):
{
"success": true,
"message": "リカバリーコードを生成しました",
"data": {
"codes": [
"ABCD-1234-EFGH",
"IJKL-5678-MNOP",
"QRST-9012-UVWX",
"YZAB-3456-CDEF",
"GHIJ-7890-KLMN",
"OPQR-1357-STUV",
"WXYZ-2468-ABCD",
"EFGH-9135-IJKL"
],
"generatedAt": "2026-01-31T10:00:00Z",
"totalCodes": 8,
"usedCodes": 0
}
}
Security Notes:
XXXX-XXXX-XXXX (12 chars, alphanumeric uppercase)Response (200):
{
"success": true,
"data": {
"hasRecoveryCodes": true,
"totalCodes": 8,
"usedCodes": 2,
"remainingCodes": 6,
"generatedAt": "2026-01-31T10:00:00Z",
"lastUsedAt": "2026-01-31T15:00:00Z"
}
}
Purpose: Verify a recovery code during account recovery flow.
Request:
{
"email": "user@example.com",
"code": "ABCD-1234-EFGH"
}
Response (200):
{
"success": true,
"message": "認証に成功しました",
"data": {
"accessToken": "jwt-token",
"refreshToken": "refresh-token",
"remainingCodes": 5,
"warning": "残りのリカバリーコードは5つです。新しいコードの生成をお勧めします。"
}
}
Security Notes:
Purpose: Allow users to regain access when primary Google login fails.
POST /api/auth/recovery/initiate
POST /api/auth/recovery/verify-backup-email
POST /api/auth/recovery/reset-auth
Purpose: Start account recovery process.
Request:
{
"email": "user@example.com" // Primary or backup email
}
Response (200):
{
"success": true,
"message": "回復方法を送信しました",
"data": {
"recoveryMethods": {
"backupEmail": true, // Masked: ba***@example.com
"recoveryCodes": true, // Has unused codes
"googleRelink": true // Can re-authorize Google
},
"primaryEmailMasked": "us***@gmail.com",
"backupEmailMasked": "ba***@example.com"
}
}
Security Notes:
Purpose: View and revoke active sessions.
CREATE TABLE user_sessions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
session_token_hash VARCHAR(255) NOT NULL,
device_info JSONB, -- {browser, os, device_type}
ip_address VARCHAR(45),
location JSONB, -- {country, city} from IP geolocation
is_current BOOLEAN DEFAULT FALSE,
last_active_at TIMESTAMP DEFAULT NOW(),
created_at TIMESTAMP DEFAULT NOW(),
expires_at TIMESTAMP NOT NULL
);
CREATE INDEX idx_sessions_user ON user_sessions(user_id);
CREATE INDEX idx_sessions_token ON user_sessions(session_token_hash);
GET /api/auth/sessions
DELETE /api/auth/sessions/:sessionId
DELETE /api/auth/sessions/all-except-current
Purpose: Allow users to unlink Google and use alternative login (requires backup first).
GET /api/auth/linked-accounts
POST /api/auth/unlink/google
POST /api/auth/link/google
Pre-conditions:
Request:
{
"confirmUnlink": true,
"password": "new-password-for-email-login" // If switching to email/password
}
Response (200):
{
"success": true,
"message": "Google連携を解除しました",
"data": {
"remainingLoginMethods": ["email_password", "recovery_codes"],
"primaryEmail": "user@example.com"
}
}
| Endpoint | Limit | Window |
|---|---|---|
| Backup email add | 3 | 1 hour |
| Verification email resend | 3 | 1 hour |
| Recovery code generation | 2 | 24 hours |
| Recovery code verify | 5 | 1 hour |
| Recovery initiate | 3 | 1 hour |
All sensitive operations MUST be logged:
{
"event_type": "recovery_code_used",
"user_id": "uuid",
"ip_address": "1.2.3.4",
"user_agent": "...",
"timestamp": "2026-01-31T10:00:00Z",
"metadata": {
"code_index": 3,
"remaining_codes": 5
}
}
Send notifications for:
Extend GET /api/auth/me response:
{
"id": "uuid",
"name": "Kenji Sakuramoto",
"email": "user@example.com",
"avatarUrl": "https://...",
"createdAt": "2025-11-27T00:00:00Z",
// New fields
"backupEmail": "ba***@example.com",
"backupEmailVerified": true,
"hasRecoveryCodes": true,
"recoveryCodesRemaining": 6,
"securityScore": 3, // 1-3 based on setup
"linkedAccounts": ["google"]
}
Score 1: Google login only (risky)
Score 2: Google + backup email OR recovery codes
Score 3: Google + backup email + recovery codes (secure)
| Phase | Feature | Effort | Impact |
|---|---|---|---|
| 1 | Backup Email | Medium | High |
| 2 | Recovery Codes | Medium | High |
| 3 | Recovery Flow | High | Critical |
| 4 | Session Management | Low | Medium |
| 5 | Account Unlinking | Medium | Low |
backup_email fields to users table/auth/me response/auth/me response| Date | Version | Changes |
|---|---|---|
| 2026-01-31 | 1.0 | Initial specification |
読み込み中
豆知識