package usecase import "context" // TOTPUseCase manages business-tier RFC 6238 TOTP enrollment and verification. // // The contract mirrors internal/model/member/SDD.md §3.5: enrollment is split // in two steps (start → confirm) so the secret is only committed after the // user proves possession; backup codes are returned exactly once on // confirmation and replenished via RegenerateBackupCodes. type TOTPUseCase interface { // StartEnroll generates a fresh secret, stashes it in a short-lived cache, // and returns the otpauth URL for QR rendering. Calling it twice replaces // the staged secret. StartEnroll(ctx context.Context, tenantID, uid, account string) (*EnrollStartDTO, error) // ConfirmEnroll validates the first user-supplied code against the staged // secret, persists the encrypted secret on the profile, and returns the // plaintext backup codes (only returned here once). ConfirmEnroll(ctx context.Context, tenantID, uid, code string) ([]string, error) // VerifyCode validates a code (TOTP or backup) for step-up. Backup codes // are single-use; replays of a successful TOTP code are rejected. VerifyCode(ctx context.Context, tenantID, uid, code string) error // Disable clears the secret and backup codes; intended to be guarded by a // step-up token in the logic layer. Disable(ctx context.Context, tenantID, uid string) error // RegenerateBackupCodes replaces existing backup codes with a new batch. RegenerateBackupCodes(ctx context.Context, tenantID, uid string) ([]string, error) // Status reports the current enrollment state for UI/admin views. Status(ctx context.Context, tenantID, uid string) (*TOTPStatusDTO, error) } // EnrollStartDTO is returned by TOTPUseCase.StartEnroll. Secret material is // never returned in plaintext outside the otpauth URL. type EnrollStartDTO struct { OtpauthURL string `json:"otpauth_url"` Issuer string `json:"issuer"` Account string `json:"account"` Digits int `json:"digits"` PeriodSec int `json:"period_seconds"` ExpiresIn int `json:"expires_in"` } // TOTPStatusDTO reports whether TOTP is active for the member and how many // backup codes remain unused. type TOTPStatusDTO struct { Enrolled bool `json:"enrolled"` EnrolledAt int64 `json:"enrolled_at,omitempty"` BackupCodesRemaining int `json:"backup_codes_remaining"` }