feat(05-02): implement EnsureConsent prompt gating --verify
- Add EnsureConsent(db, in, out) that returns (true, nil) immediately if verify.consent==granted, otherwise prompts once, reads a line, persists 'granted' on 'yes' (case-insensitive), 'declined' otherwise. - Declined is not sticky — next call re-prompts; only granted persists. - Prompt references legal implications and directs users to 'keyhunter legal'.
This commit is contained in:
70
pkg/verify/consent.go
Normal file
70
pkg/verify/consent.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package verify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/salvacybersec/keyhunter/pkg/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConsentSettingKey is the settings table key under which the user's
|
||||||
|
// verification consent decision is persisted.
|
||||||
|
const ConsentSettingKey = "verify.consent"
|
||||||
|
|
||||||
|
// Consent decision values stored under ConsentSettingKey.
|
||||||
|
const (
|
||||||
|
ConsentGranted = "granted"
|
||||||
|
ConsentDeclined = "declined"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EnsureConsent checks whether the user has previously granted consent to run
|
||||||
|
// active key verification. If consent was previously granted it returns
|
||||||
|
// (true, nil) immediately without prompting. Otherwise it writes a warning
|
||||||
|
// and prompt to out, reads one line from in, and persists the decision via
|
||||||
|
// the settings table.
|
||||||
|
//
|
||||||
|
// Declined decisions are not sticky — a subsequent call will re-prompt. Only
|
||||||
|
// "granted" persists across runs.
|
||||||
|
func EnsureConsent(db *storage.DB, in io.Reader, out io.Writer) (bool, error) {
|
||||||
|
val, found, err := db.GetSetting(ConsentSettingKey)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("reading verify.consent: %w", err)
|
||||||
|
}
|
||||||
|
if found && val == ConsentGranted {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(out, "⚠ Active Key Verification — Legal Notice")
|
||||||
|
fmt.Fprintln(out, "")
|
||||||
|
fmt.Fprintln(out, "Using --verify will send HTTP requests to third-party provider APIs")
|
||||||
|
fmt.Fprintln(out, "for every API key KeyHunter finds. You are responsible for the")
|
||||||
|
fmt.Fprintln(out, "legal implications of these requests in your jurisdiction")
|
||||||
|
fmt.Fprintln(out, "(CFAA, Computer Misuse Act, GDPR, provider ToS).")
|
||||||
|
fmt.Fprintln(out, "")
|
||||||
|
fmt.Fprintln(out, "Run `keyhunter legal` to read the full disclaimer.")
|
||||||
|
fmt.Fprintln(out, "")
|
||||||
|
fmt.Fprint(out, "Type 'yes' to proceed: ")
|
||||||
|
|
||||||
|
reader := bufio.NewReader(in)
|
||||||
|
line, err := reader.ReadString('\n')
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return false, fmt.Errorf("reading consent input: %w", err)
|
||||||
|
}
|
||||||
|
answer := strings.ToLower(strings.TrimSpace(line))
|
||||||
|
|
||||||
|
if answer == "yes" {
|
||||||
|
if err := db.SetSetting(ConsentSettingKey, ConsentGranted); err != nil {
|
||||||
|
return false, fmt.Errorf("persisting consent: %w", err)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(out, "Consent recorded. Proceeding with verification.")
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.SetSetting(ConsentSettingKey, ConsentDeclined); err != nil {
|
||||||
|
return false, fmt.Errorf("persisting declined consent: %w", err)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(out, "Consent declined. Verification skipped.")
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user