51 lines
2.0 KiB
Go
51 lines
2.0 KiB
Go
|
|
package repository
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
// TOTPProfileRecord captures the persisted TOTP state for a member.
|
||
|
|
//
|
||
|
|
// SecretCipher is the AES-GCM blob; BackupCodesHash holds bcrypt hashes of
|
||
|
|
// the plaintext backup codes (single-use).
|
||
|
|
type TOTPProfileRecord struct {
|
||
|
|
Enrolled bool
|
||
|
|
SecretCipher []byte
|
||
|
|
BackupCodesHash []string
|
||
|
|
EnrolledAt int64
|
||
|
|
}
|
||
|
|
|
||
|
|
// TOTPProfileRepository persists per-member TOTP secrets and backup codes.
|
||
|
|
//
|
||
|
|
// Implementations must be safe to call concurrently for distinct (tenant, uid)
|
||
|
|
// pairs; concurrent calls for the same member should be linearised by the
|
||
|
|
// caller (the usecase layer) before mutation.
|
||
|
|
type TOTPProfileRepository interface {
|
||
|
|
Get(ctx context.Context, tenantID, uid string) (*TOTPProfileRecord, error)
|
||
|
|
Save(ctx context.Context, tenantID, uid string, rec *TOTPProfileRecord) error
|
||
|
|
Clear(ctx context.Context, tenantID, uid string) error
|
||
|
|
|
||
|
|
// ConsumeBackupCode removes the supplied backup-code hash atomically and
|
||
|
|
// returns true when removal succeeded.
|
||
|
|
ConsumeBackupCode(ctx context.Context, tenantID, uid, hash string) (bool, error)
|
||
|
|
// ReplaceBackupCodes overwrites the backup-code hashes in one shot.
|
||
|
|
ReplaceBackupCodes(ctx context.Context, tenantID, uid string, hashes []string) error
|
||
|
|
}
|
||
|
|
|
||
|
|
// TOTPEnrollStore stages a freshly generated secret until the user confirms
|
||
|
|
// the first code. The blob is short-lived (Redis TTL).
|
||
|
|
type TOTPEnrollStore interface {
|
||
|
|
Save(ctx context.Context, tenantID, uid string, cipher []byte, ttl time.Duration) error
|
||
|
|
Get(ctx context.Context, tenantID, uid string) ([]byte, error)
|
||
|
|
Delete(ctx context.Context, tenantID, uid string) error
|
||
|
|
}
|
||
|
|
|
||
|
|
// TOTPReplayStore prevents replay of a successful TOTP code within its
|
||
|
|
// allowed verification window.
|
||
|
|
type TOTPReplayStore interface {
|
||
|
|
// MarkUsed returns true when the key was newly recorded (i.e. the code
|
||
|
|
// had not been used yet); false means it was already consumed.
|
||
|
|
MarkUsed(ctx context.Context, tenantID, uid string, timestep uint64, ttl time.Duration) (bool, error)
|
||
|
|
}
|