mirror of
https://github.com/anotherhadi/iknowyou.git
synced 2026-04-11 16:37:25 +02:00
163 lines
5.2 KiB
Go
163 lines
5.2 KiB
Go
package breachdirectory
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/anotherhadi/iknowyou/internal/tools"
|
|
)
|
|
|
|
const (
|
|
name = "breachdirectory"
|
|
description = "Data breach search via BreachDirectory — checks if an email, username, or phone appears in known data breaches and returns exposed passwords/hashes."
|
|
link = "https://breachdirectory.org"
|
|
icon = ""
|
|
)
|
|
|
|
type Config struct {
|
|
APIKey string `yaml:"api_key" iky:"desc=RapidAPI key for BreachDirectory (required — get one at rapidapi.com/rohan-patra/api/breachdirectory);required=true"`
|
|
}
|
|
|
|
type Runner struct {
|
|
cfg Config
|
|
}
|
|
|
|
func New() tools.ToolRunner {
|
|
cfg := Config{}
|
|
tools.ApplyDefaults(&cfg)
|
|
return &Runner{cfg: cfg}
|
|
}
|
|
|
|
func (r *Runner) Name() string { return name }
|
|
func (r *Runner) Description() string { return description }
|
|
func (r *Runner) Link() string { return link }
|
|
func (r *Runner) Icon() string { return icon }
|
|
|
|
func (r *Runner) InputTypes() []tools.InputType {
|
|
return []tools.InputType{
|
|
tools.InputTypeEmail,
|
|
tools.InputTypeUsername,
|
|
}
|
|
}
|
|
|
|
func (r *Runner) ConfigPtr() interface{} { return &r.cfg }
|
|
|
|
func (r *Runner) ConfigFields() []tools.ConfigField {
|
|
return tools.ReflectConfigFields(r.cfg)
|
|
}
|
|
|
|
type bdResponse struct {
|
|
Success bool `json:"success"`
|
|
Found int `json:"found"`
|
|
Result json.RawMessage `json:"result"`
|
|
}
|
|
|
|
type bdEntry struct {
|
|
Email string `json:"email"`
|
|
Password string `json:"password"`
|
|
Hash string `json:"hash"`
|
|
SHA1 string `json:"sha1"`
|
|
Sources string `json:"sources"`
|
|
HasPassword bool `json:"has_password"`
|
|
}
|
|
|
|
func (r *Runner) Run(ctx context.Context, target string, _ tools.InputType, out chan<- tools.Event) error {
|
|
defer close(out)
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet,
|
|
"https://breachdirectory.p.rapidapi.com/?func=auto&term="+target, nil)
|
|
if err != nil {
|
|
out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: err.Error()}
|
|
out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
|
|
out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
|
|
return nil
|
|
}
|
|
req.Header.Set("X-RapidAPI-Key", r.cfg.APIKey)
|
|
req.Header.Set("X-RapidAPI-Host", "breachdirectory.p.rapidapi.com")
|
|
req.Header.Set("Accept", "application/json")
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
if ctx.Err() != nil {
|
|
out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "scan cancelled"}
|
|
} else {
|
|
out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: err.Error()}
|
|
}
|
|
out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
|
|
out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
|
|
return nil
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "failed to read response"}
|
|
out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
|
|
out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
|
|
return nil
|
|
}
|
|
|
|
if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
|
|
out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "invalid or exhausted API key"}
|
|
out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
|
|
out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
|
|
return nil
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: fmt.Sprintf("API error %d: %s", resp.StatusCode, string(body))}
|
|
out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
|
|
out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
|
|
return nil
|
|
}
|
|
|
|
var parsed bdResponse
|
|
if err := json.Unmarshal(body, &parsed); err != nil {
|
|
out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "failed to parse response"}
|
|
out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
|
|
out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
|
|
return nil
|
|
}
|
|
|
|
if !parsed.Success || parsed.Found == 0 {
|
|
out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
|
|
out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
|
|
return nil
|
|
}
|
|
|
|
var entries []bdEntry
|
|
if err := json.Unmarshal(parsed.Result, &entries); err != nil || len(entries) == 0 {
|
|
out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
|
|
out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
|
|
return nil
|
|
}
|
|
|
|
var sb strings.Builder
|
|
sb.WriteString(fmt.Sprintf("Found in %d breach record(s)\n\n", parsed.Found))
|
|
|
|
for _, entry := range entries {
|
|
if entry.Sources != "" {
|
|
sb.WriteString(fmt.Sprintf("Source: %s\n", entry.Sources))
|
|
}
|
|
if entry.Password != "" {
|
|
sb.WriteString(fmt.Sprintf("Password: %s\n", entry.Password))
|
|
}
|
|
if entry.Hash != "" {
|
|
sb.WriteString(fmt.Sprintf("Hash: %s\n", entry.Hash))
|
|
}
|
|
if entry.SHA1 != "" {
|
|
sb.WriteString(fmt.Sprintf("SHA1: %s\n", entry.SHA1))
|
|
}
|
|
sb.WriteString("\n")
|
|
}
|
|
|
|
out <- tools.Event{Tool: name, Type: tools.EventTypeOutput, Payload: strings.TrimSpace(sb.String())}
|
|
out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: parsed.Found}
|
|
out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
|
|
return nil
|
|
}
|