mirror of
https://github.com/anotherhadi/iknowyou.git
synced 2026-04-12 00:47:26 +02:00
init
This commit is contained in:
178
back/internal/tools/leakcheck/tool.go
Normal file
178
back/internal/tools/leakcheck/tool.go
Normal file
@@ -0,0 +1,178 @@
|
||||
package leakcheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/anotherhadi/iknowyou/internal/tools"
|
||||
)
|
||||
|
||||
const (
|
||||
name = "leakcheck"
|
||||
description = "Data breach lookup via LeakCheck.io — searches 7B+ leaked records for email addresses, usernames, and phone numbers across hundreds of breaches."
|
||||
link = "https://leakcheck.io"
|
||||
icon = ""
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
APIKey string `yaml:"api_key" iky:"desc=LeakCheck API key (required — get one at leakcheck.io);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,
|
||||
tools.InputTypePhone,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Runner) ConfigPtr() interface{} { return &r.cfg }
|
||||
|
||||
func (r *Runner) ConfigFields() []tools.ConfigField {
|
||||
return tools.ReflectConfigFields(r.cfg)
|
||||
}
|
||||
|
||||
type leakCheckResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Found int `json:"found"`
|
||||
Result []struct {
|
||||
Email string `json:"email"`
|
||||
Username string `json:"username"`
|
||||
Phone string `json:"phone"`
|
||||
Password string `json:"password"`
|
||||
Hash string `json:"hash"`
|
||||
Sources []string `json:"sources"`
|
||||
Fields []string `json:"fields"`
|
||||
} `json:"result"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
func (r *Runner) Run(ctx context.Context, target string, inputType tools.InputType, out chan<- tools.Event) error {
|
||||
defer close(out)
|
||||
|
||||
queryType := "auto"
|
||||
switch inputType {
|
||||
case tools.InputTypeEmail:
|
||||
queryType = "email"
|
||||
case tools.InputTypeUsername:
|
||||
queryType = "login"
|
||||
case tools.InputTypePhone:
|
||||
queryType = "phone"
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("https://leakcheck.io/api/v2/query/%s?type=%s", target, queryType)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, 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-API-Key", r.cfg.APIKey)
|
||||
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
|
||||
}
|
||||
|
||||
var result leakCheckResponse
|
||||
if err := json.Unmarshal(body, &result); 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 !result.Success {
|
||||
msg := result.Error
|
||||
if msg == "" {
|
||||
msg = "API returned failure"
|
||||
}
|
||||
out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: msg}
|
||||
out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
|
||||
out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
|
||||
return nil
|
||||
}
|
||||
|
||||
if result.Found == 0 || len(result.Result) == 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(es)\n\n", result.Found))
|
||||
|
||||
for _, entry := range result.Result {
|
||||
if len(entry.Sources) > 0 {
|
||||
sb.WriteString(fmt.Sprintf("Sources: %s\n", strings.Join(entry.Sources, ", ")))
|
||||
}
|
||||
if entry.Email != "" {
|
||||
sb.WriteString(fmt.Sprintf(" Email: %s\n", entry.Email))
|
||||
}
|
||||
if entry.Username != "" {
|
||||
sb.WriteString(fmt.Sprintf(" Username: %s\n", entry.Username))
|
||||
}
|
||||
if entry.Phone != "" {
|
||||
sb.WriteString(fmt.Sprintf(" Phone: %s\n", entry.Phone))
|
||||
}
|
||||
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 len(entry.Fields) > 0 {
|
||||
sb.WriteString(fmt.Sprintf(" Fields: %s\n", strings.Join(entry.Fields, ", ")))
|
||||
}
|
||||
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: result.Found}
|
||||
out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user