Init gravatar recon
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
This commit is contained in:
@@ -4,6 +4,7 @@ go 1.25.0
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/anotherhadi/github-recon v1.5.6
|
github.com/anotherhadi/github-recon v1.5.6
|
||||||
|
github.com/anotherhadi/gravatar-recon v1.0.1
|
||||||
github.com/charmbracelet/log v0.4.2
|
github.com/charmbracelet/log v0.4.2
|
||||||
github.com/gin-gonic/gin v1.10.1
|
github.com/gin-gonic/gin v1.10.1
|
||||||
github.com/marcboeker/go-duckdb v1.8.5
|
github.com/marcboeker/go-duckdb v1.8.5
|
||||||
@@ -49,7 +50,7 @@ require (
|
|||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||||
github.com/saran13raj/go-pixels v0.0.0-20250629121333-58b240a3ae51 // indirect
|
github.com/saran13raj/go-pixels v0.0.0-20250629121333-58b240a3ae51 // indirect
|
||||||
github.com/spf13/pflag v1.0.7 // indirect
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7X
|
|||||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||||
github.com/anotherhadi/github-recon v1.5.6 h1:IN3lQZRqqNbPpSyP5fvNoJrYODbM2tNwS5tiRgD+i1s=
|
github.com/anotherhadi/github-recon v1.5.6 h1:IN3lQZRqqNbPpSyP5fvNoJrYODbM2tNwS5tiRgD+i1s=
|
||||||
github.com/anotherhadi/github-recon v1.5.6/go.mod h1:E2tmCmjEZdJeBx8u1J8sSMtnmU8aDQ6IjCoq3ykoHtY=
|
github.com/anotherhadi/github-recon v1.5.6/go.mod h1:E2tmCmjEZdJeBx8u1J8sSMtnmU8aDQ6IjCoq3ykoHtY=
|
||||||
|
github.com/anotherhadi/gravatar-recon v1.0.1 h1:Js3NCrVXhJb/ShG6PMzma1bsER0lQi9qbFHK1uABMm4=
|
||||||
|
github.com/anotherhadi/gravatar-recon v1.0.1/go.mod h1:cMP1mqW5vxwRCIZDbQGr0gb/SzbAveM2EcdJ/BwXQN8=
|
||||||
github.com/apache/arrow-go/v18 v18.1.0 h1:agLwJUiVuwXZdwPYVrlITfx7bndULJ/dggbnLFgDp/Y=
|
github.com/apache/arrow-go/v18 v18.1.0 h1:agLwJUiVuwXZdwPYVrlITfx7bndULJ/dggbnLFgDp/Y=
|
||||||
github.com/apache/arrow-go/v18 v18.1.0/go.mod h1:tigU/sIgKNXaesf5d7Y95jBBKS5KsxTqYBKXFsvKzo0=
|
github.com/apache/arrow-go/v18 v18.1.0/go.mod h1:tigU/sIgKNXaesf5d7Y95jBBKS5KsxTqYBKXFsvKzo0=
|
||||||
github.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE=
|
github.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE=
|
||||||
@@ -115,8 +117,8 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t
|
|||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/saran13raj/go-pixels v0.0.0-20250629121333-58b240a3ae51 h1:H/XUfYcLxI3CBmDlgBpnOeTntRgqWvIoUXnqhCF5a0s=
|
github.com/saran13raj/go-pixels v0.0.0-20250629121333-58b240a3ae51 h1:H/XUfYcLxI3CBmDlgBpnOeTntRgqWvIoUXnqhCF5a0s=
|
||||||
github.com/saran13raj/go-pixels v0.0.0-20250629121333-58b240a3ae51/go.mod h1:sqhdZVLvqzTEBtmZBuTnFDUW0Lsryw2X2/wrLgqLEYg=
|
github.com/saran13raj/go-pixels v0.0.0-20250629121333-58b240a3ae51/go.mod h1:sqhdZVLvqzTEBtmZBuTnFDUW0Lsryw2X2/wrLgqLEYg=
|
||||||
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||||
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
|||||||
@@ -15,13 +15,14 @@ type LeakResult struct {
|
|||||||
Duration time.Duration
|
Duration time.Duration
|
||||||
Rows []map[string]string
|
Rows []map[string]string
|
||||||
Error string
|
Error string
|
||||||
|
Inactive bool
|
||||||
LimitHit bool // Whether the search hit the limit
|
LimitHit bool // Whether the search hit the limit
|
||||||
}
|
}
|
||||||
|
|
||||||
func Search(s *server.Server, queryText, column string, exactMatch bool) LeakResult {
|
func Search(s *server.Server, queryText, column string, exactMatch bool) LeakResult {
|
||||||
if len(*(s.Dataleaks)) == 0 {
|
if len(*(s.Dataleaks)) == 0 {
|
||||||
return LeakResult{
|
return LeakResult{
|
||||||
Error: "No dataleak configured",
|
Inactive: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|||||||
@@ -14,15 +14,13 @@ import (
|
|||||||
type GithubResult struct {
|
type GithubResult struct {
|
||||||
Duration time.Duration
|
Duration time.Duration
|
||||||
Error string
|
Error string
|
||||||
|
Inactive bool
|
||||||
|
|
||||||
UsernameResult *recon_username.UsernameResult
|
UsernameResult *recon_username.UsernameResult
|
||||||
EmailResult *recon_email.EmailResult
|
EmailResult *recon_email.EmailResult
|
||||||
}
|
}
|
||||||
|
|
||||||
func Search(s *server.Server, queryText, column string) *GithubResult {
|
func GithubSearch(s *server.Server, queryText, queryType string) GithubResult {
|
||||||
if !s.Settings.GithubRecon {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
gr := GithubResult{}
|
gr := GithubResult{}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
settings := github_recon_settings.GetDefaultSettings()
|
settings := github_recon_settings.GetDefaultSettings()
|
||||||
@@ -35,15 +33,12 @@ func Search(s *server.Server, queryText, column string) *GithubResult {
|
|||||||
|
|
||||||
queryText = strings.TrimSpace(queryText)
|
queryText = strings.TrimSpace(queryText)
|
||||||
|
|
||||||
if column == "email" || strings.HasSuffix(column, "_email") ||
|
if queryType == "email" {
|
||||||
column == "username" || strings.HasSuffix(column, "_username") ||
|
|
||||||
column == "" || column == "all" {
|
|
||||||
if isValidEmail(queryText) {
|
|
||||||
settings.Target = queryText
|
settings.Target = queryText
|
||||||
settings.TargetType = github_recon_settings.TargetEmail
|
settings.TargetType = github_recon_settings.TargetEmail
|
||||||
result := recon_email.Email(settings)
|
result := recon_email.Email(settings)
|
||||||
gr.EmailResult = &result
|
gr.EmailResult = &result
|
||||||
} else if isValidUsername(queryText) {
|
} else if queryType == "username" {
|
||||||
settings.Target = queryText
|
settings.Target = queryText
|
||||||
settings.TargetType = github_recon_settings.TargetUsername
|
settings.TargetType = github_recon_settings.TargetUsername
|
||||||
result, err := recon_username.Username(settings)
|
result, err := recon_username.Username(settings)
|
||||||
@@ -56,35 +51,9 @@ func Search(s *server.Server, queryText, column string) *GithubResult {
|
|||||||
gr.UsernameResult = &result
|
gr.UsernameResult = &result
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return GithubResult{Inactive: true}
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gr.Duration = time.Since(now)
|
gr.Duration = time.Since(now)
|
||||||
return &gr
|
return gr
|
||||||
}
|
|
||||||
|
|
||||||
func isValidEmail(email string) bool {
|
|
||||||
if !strings.Contains(email, "@") || !strings.Contains(email, ".") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(email, "@") || strings.HasSuffix(email, "@") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if strings.Contains(email, " ") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func isValidUsername(username string) bool {
|
|
||||||
if len(username) < 1 || len(username) > 39 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if strings.Contains(username, " ") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|||||||
32
back/search/osint/gravatar.go
Normal file
32
back/search/osint/gravatar.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package osint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/anotherhadi/eleakxir/backend/server"
|
||||||
|
gravatar_recon "github.com/anotherhadi/gravatar-recon"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GravatarResult struct {
|
||||||
|
Duration time.Duration
|
||||||
|
Error string
|
||||||
|
Inactive bool
|
||||||
|
|
||||||
|
Results []gravatar_recon.GravatarProfile
|
||||||
|
}
|
||||||
|
|
||||||
|
func GravatarSearch(s *server.Server, queryText string) GravatarResult {
|
||||||
|
gr := GravatarResult{}
|
||||||
|
now := time.Now()
|
||||||
|
results, err := gravatar_recon.GetGravatarProfiles(queryText)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
gr.Error = err.Error()
|
||||||
|
return gr
|
||||||
|
}
|
||||||
|
|
||||||
|
gr.Results = *results
|
||||||
|
|
||||||
|
gr.Duration = time.Since(now)
|
||||||
|
return gr
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ type Query struct {
|
|||||||
// Services
|
// Services
|
||||||
Datawells bool // Whether to include datawells in the search
|
Datawells bool // Whether to include datawells in the search
|
||||||
GithubRecon bool // Whether to include github-recon in the search
|
GithubRecon bool // Whether to include github-recon in the search
|
||||||
|
GravatarRecon bool // Whether to include gravatar-recon in the search
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result struct {
|
type Result struct {
|
||||||
@@ -31,25 +32,23 @@ type Result struct {
|
|||||||
|
|
||||||
LeakResult dataleak.LeakResult
|
LeakResult dataleak.LeakResult
|
||||||
GithubResult osint.GithubResult
|
GithubResult osint.GithubResult
|
||||||
|
GravatarResult osint.GravatarResult
|
||||||
}
|
}
|
||||||
|
|
||||||
func Search(s *server.Server, q Query, r *Result, mu *sync.RWMutex) {
|
func Search(s *server.Server, q Query, r *Result, mu *sync.RWMutex) {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
cleanQueryText := strings.TrimPrefix(q.Text, "^")
|
|
||||||
cleanQueryText = strings.TrimSuffix(q.Text, "$")
|
|
||||||
|
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
r.Date = time.Now()
|
r.Date = time.Now()
|
||||||
r.Status = "pending"
|
r.Status = "pending"
|
||||||
r.Query = q
|
r.Query = q
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
|
|
||||||
wg.Add(2)
|
wg.Add(3)
|
||||||
go func() {
|
go func() {
|
||||||
if !q.Datawells {
|
if !q.Datawells {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
r.LeakResult = dataleak.LeakResult{Error: "not enabled"}
|
r.LeakResult = dataleak.LeakResult{Inactive: true}
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
wg.Done()
|
wg.Done()
|
||||||
return
|
return
|
||||||
@@ -61,21 +60,52 @@ func Search(s *server.Server, q Query, r *Result, mu *sync.RWMutex) {
|
|||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
cleanQueryText := strings.TrimPrefix(q.Text, "^")
|
||||||
|
cleanQueryText = strings.TrimSuffix(q.Text, "$")
|
||||||
|
isEmail := false
|
||||||
|
isUsername := false
|
||||||
|
|
||||||
|
if q.Column == "email" || strings.HasSuffix(q.Column, "_email") ||
|
||||||
|
q.Column == "username" || strings.HasSuffix(q.Column, "_username") ||
|
||||||
|
q.Column == "" || q.Column == "all" {
|
||||||
|
if isValidEmail(cleanQueryText) {
|
||||||
|
isEmail = true
|
||||||
|
} else if isValidUsername(cleanQueryText) {
|
||||||
|
isUsername = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if !q.GithubRecon {
|
if !q.GithubRecon || !s.Settings.GithubRecon || (!isEmail && !isUsername) {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
r.GithubResult = osint.GithubResult{Error: "not enabled"}
|
r.GithubResult = osint.GithubResult{Inactive: true}
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
wg.Done()
|
wg.Done()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
githubResult := osint.Search(s, cleanQueryText, q.Column)
|
var githubResult osint.GithubResult
|
||||||
mu.Lock()
|
if isEmail {
|
||||||
if githubResult == nil {
|
githubResult = osint.GithubSearch(s, cleanQueryText, "email")
|
||||||
r.GithubResult = osint.GithubResult{}
|
} else if isUsername {
|
||||||
} else {
|
githubResult = osint.GithubSearch(s, cleanQueryText, "username")
|
||||||
r.GithubResult = *githubResult
|
|
||||||
}
|
}
|
||||||
|
mu.Lock()
|
||||||
|
r.GithubResult = githubResult
|
||||||
|
mu.Unlock()
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if !q.GravatarRecon || !s.Settings.GravatarRecon || !isEmail {
|
||||||
|
mu.Lock()
|
||||||
|
r.GravatarResult = osint.GravatarResult{Inactive: true}
|
||||||
|
mu.Unlock()
|
||||||
|
wg.Done()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gravatarResult := osint.GravatarSearch(s, cleanQueryText)
|
||||||
|
mu.Lock()
|
||||||
|
r.GravatarResult = gravatarResult
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
@@ -91,3 +121,26 @@ func EncodeQueryID(q Query, dataleaksCount uint64) string {
|
|||||||
raw, _ := json.Marshal(q)
|
raw, _ := json.Marshal(q)
|
||||||
return fmt.Sprintf("%d:%s", dataleaksCount, base64.URLEncoding.EncodeToString(raw))
|
return fmt.Sprintf("%d:%s", dataleaksCount, base64.URLEncoding.EncodeToString(raw))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isValidEmail(email string) bool {
|
||||||
|
if !strings.Contains(email, "@") || !strings.Contains(email, ".") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(email, "@") || strings.HasSuffix(email, "@") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if strings.Contains(email, " ") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidUsername(username string) bool {
|
||||||
|
if len(username) < 1 || len(username) > 39 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if strings.Contains(username, " ") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ type ServerSettings struct {
|
|||||||
GithubToken string `json:"-"` // Github token for github-recon
|
GithubToken string `json:"-"` // Github token for github-recon
|
||||||
GithubTokenLoaded bool
|
GithubTokenLoaded bool
|
||||||
GithubDeepMode bool // Deep mode for github-recon
|
GithubDeepMode bool // Deep mode for github-recon
|
||||||
|
GravatarRecon bool // Activate gravatar-recon OSINT tool
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadServerSettings() ServerSettings {
|
func LoadServerSettings() ServerSettings {
|
||||||
@@ -49,6 +50,7 @@ func LoadServerSettings() ServerSettings {
|
|||||||
GithubRecon: getEnvBoolOrDefault("GITHUB_RECON", true),
|
GithubRecon: getEnvBoolOrDefault("GITHUB_RECON", true),
|
||||||
GithubToken: getEnvStringOrDefault("GITHUB_TOKEN", "null"),
|
GithubToken: getEnvStringOrDefault("GITHUB_TOKEN", "null"),
|
||||||
GithubDeepMode: getEnvBoolOrDefault("GITHUB_DEEP_MODE", false),
|
GithubDeepMode: getEnvBoolOrDefault("GITHUB_DEEP_MODE", false),
|
||||||
|
GravatarRecon: getEnvBoolOrDefault("GRAVATAR_RECON", true),
|
||||||
}
|
}
|
||||||
|
|
||||||
if ss.GithubToken == "null" || strings.TrimSpace(ss.GithubToken) == "" {
|
if ss.GithubToken == "null" || strings.TrimSpace(ss.GithubToken) == "" {
|
||||||
|
|||||||
104
front/src/lib/components/index/search/id/gravatarResult.svelte
Normal file
104
front/src/lib/components/index/search/id/gravatarResult.svelte
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Accordion from "$src/lib/components/accordion.svelte";
|
||||||
|
import Table from "$src/lib/components/table.svelte";
|
||||||
|
import type { GravatarResult } from "$src/lib/types";
|
||||||
|
import { Contact, ExternalLink, Mail, Phone } from "@lucide/svelte";
|
||||||
|
|
||||||
|
const { result }: { result: GravatarResult } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="w-full flex flex-col gap-10">
|
||||||
|
{#each result.Results as r}
|
||||||
|
<div class="flex flex-wrap gap-5">
|
||||||
|
<div class="avatar">
|
||||||
|
<div class="w-24 h-24 rounded-xl">
|
||||||
|
<img src={r.thumbnailUrl} alt="Avatar of {r.preferredUsername}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<h3 class="h3">{r.displayName}</h3>
|
||||||
|
<p class="text-base-content/60">
|
||||||
|
@{r.preferredUsername}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p class="max-w-sm">{r.aboutMe}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card card-border border-neutral shadow">
|
||||||
|
<div class="grid">
|
||||||
|
<Table
|
||||||
|
row={{
|
||||||
|
profile_url: r.profileUrl,
|
||||||
|
current_location: r.currentLocation,
|
||||||
|
job_title: r.job_title,
|
||||||
|
company: r.company,
|
||||||
|
pronouns: r.pronouns,
|
||||||
|
pronunciation: r.pronunciation,
|
||||||
|
photos: r.photos.length > 0 ? r.photos.length : "N/A",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
{#if r.accounts && r.accounts.length > 0}
|
||||||
|
<div>
|
||||||
|
<h4 class="h4 mb-2">Social Links</h4>
|
||||||
|
<ul class="flex gap-4 flex-col mt-4 mb-6">
|
||||||
|
{#each r.accounts as account}
|
||||||
|
<a href={account.url} target="_blank" rel="noopener noreferrer">
|
||||||
|
<div class="badge bg-base-300">
|
||||||
|
<ExternalLink size={12} />
|
||||||
|
{account.username} ({account.url})
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if r.emails && r.emails.length > 0}
|
||||||
|
<div>
|
||||||
|
<ul class="list bg-base-100 rounded-box shadow-md">
|
||||||
|
<Accordion
|
||||||
|
icon={Mail}
|
||||||
|
title={"Emails"}
|
||||||
|
subtitle={r.emails.length + " email found"}
|
||||||
|
>
|
||||||
|
<Table row={r.emails} />
|
||||||
|
</Accordion>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if r.phoneNumbers && r.phoneNumbers.length > 0}
|
||||||
|
<div>
|
||||||
|
<ul class="list bg-base-100 rounded-box shadow-md">
|
||||||
|
<Accordion
|
||||||
|
icon={Phone}
|
||||||
|
title={"Phone Numbers"}
|
||||||
|
subtitle={r.phoneNumbers.length + " phone numbers found"}
|
||||||
|
>
|
||||||
|
<Table row={r.phoneNumbers} />
|
||||||
|
</Accordion>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if r.contactInfo && r.contactInfo.length > 0}
|
||||||
|
<div>
|
||||||
|
<ul class="list bg-base-100 rounded-box shadow-md">
|
||||||
|
<Accordion
|
||||||
|
icon={Contact}
|
||||||
|
title={"Contact Info"}
|
||||||
|
subtitle={r.contactInfo.length + " contact info found"}
|
||||||
|
>
|
||||||
|
<Table row={r.contactInfo} />
|
||||||
|
</Accordion>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
result.GithubResult.EmailResult?.Commits?.length | 0,
|
result.GithubResult.EmailResult?.Commits?.length | 0,
|
||||||
result.GithubResult.EmailResult?.Spoofing ? 1 : 0,
|
result.GithubResult.EmailResult?.Spoofing ? 1 : 0,
|
||||||
result.GithubResult.UsernameResult?.Commits?.length | 0,
|
result.GithubResult.UsernameResult?.Commits?.length | 0,
|
||||||
|
result.GravatarResult.Results?.length | 0,
|
||||||
];
|
];
|
||||||
nresult = r.reduce((a, b) => a + b, 0);
|
nresult = r.reduce((a, b) => a + b, 0);
|
||||||
});
|
});
|
||||||
@@ -45,9 +46,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="stat-title">Status</div>
|
<div class="stat-title">Status</div>
|
||||||
<div class="stat-value" class:animate-pulse={result.Status === "pending"}>
|
<div class="stat-value" class:animate-pulse={result.Status === "pending"}>
|
||||||
{result.Status}
|
|
||||||
{#if result.Status === "pending"}
|
{#if result.Status === "pending"}
|
||||||
|
Pending
|
||||||
<span class="loading loading-dots loading-xs ml-2"></span>
|
<span class="loading loading-dots loading-xs ml-2"></span>
|
||||||
|
{:else if result.Status === "completed" && nresult === 0}
|
||||||
|
No results
|
||||||
|
{:else if result.Status === "completed" && nresult > 0}
|
||||||
|
Completed
|
||||||
|
{:else}
|
||||||
|
{result.Status}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,12 +11,14 @@
|
|||||||
initialExactMatch = false,
|
initialExactMatch = false,
|
||||||
initialDatawells = true,
|
initialDatawells = true,
|
||||||
initialGithubRecon = true,
|
initialGithubRecon = true,
|
||||||
|
initialGravatarRecon = true,
|
||||||
}: {
|
}: {
|
||||||
initialQuery?: string;
|
initialQuery?: string;
|
||||||
initialFilter?: string;
|
initialFilter?: string;
|
||||||
initialExactMatch?: boolean;
|
initialExactMatch?: boolean;
|
||||||
initialDatawells?: boolean;
|
initialDatawells?: boolean;
|
||||||
initialGithubRecon?: boolean;
|
initialGithubRecon?: boolean;
|
||||||
|
initialGravatarRecon?: boolean;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let filters = [
|
let filters = [
|
||||||
@@ -35,12 +37,13 @@
|
|||||||
let exactMatch = $state<boolean>(initialExactMatch);
|
let exactMatch = $state<boolean>(initialExactMatch);
|
||||||
let datawells = $state<boolean>(initialDatawells);
|
let datawells = $state<boolean>(initialDatawells);
|
||||||
let githubRecon = $state<boolean>(initialGithubRecon);
|
let githubRecon = $state<boolean>(initialGithubRecon);
|
||||||
|
let gravatarRecon = $state<boolean>(initialGravatarRecon);
|
||||||
|
|
||||||
function NewSearch() {
|
function NewSearch() {
|
||||||
axios
|
axios
|
||||||
.post(
|
.post(
|
||||||
`${$serverUrl}/search`,
|
`${$serverUrl}/search`,
|
||||||
{ Text: query, Column: activeFilter, ExactMatch: exactMatch, Datawells: datawells, GithubRecon: githubRecon },
|
{ Text: query, Column: activeFilter, ExactMatch: exactMatch, Datawells: datawells, GithubRecon: githubRecon, GravatarRecon: gravatarRecon },
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -98,6 +101,12 @@
|
|||||||
Github Recon
|
Github Recon
|
||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<label class="label">
|
||||||
|
<input type="checkbox" bind:checked={gravatarRecon} class="checkbox" />
|
||||||
|
Gravatar Recon
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -60,9 +60,9 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="hover:bg-base-300">
|
<tr class="hover:bg-base-300">
|
||||||
<th>Google hunt</th>
|
<th>Gravatar recon</th>
|
||||||
<td>
|
<td>
|
||||||
{#if serverInfo.Settings.GithubRecon === true}
|
{#if serverInfo.Settings.GravatarRecon === true}
|
||||||
<div class="inline-grid *:[grid-area:1/1] mr-2">
|
<div class="inline-grid *:[grid-area:1/1] mr-2">
|
||||||
<div class="status status-success"></div>
|
<div class="status status-success"></div>
|
||||||
<div class="status status-success"></div>
|
<div class="status status-success"></div>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
{#each Object.entries(item) as [key, value]}
|
{#each Object.entries(item) as [key, value]}
|
||||||
<th class="text-xs whitespace-nowrap font-semibold opacity-60">
|
<th class="text-xs whitespace-nowrap font-semibold opacity-60">
|
||||||
{#if key.toLowerCase() === "url" && value !== "" && value !== null}
|
{#if ( key.toLowerCase() == "url" || key.toLowerCase().endsWith("_url")) && value !== null && value !== ""}
|
||||||
<a
|
<a
|
||||||
href={value}
|
href={value}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each Object.entries(row) as [key, value]}
|
{#each Object.entries(row) as [key, value]}
|
||||||
{#if key !== "source" && value !== "" && value !== null}
|
{#if key !== "source" && value !== null && value !== ""}
|
||||||
<tr class="">
|
<tr class="">
|
||||||
<th
|
<th
|
||||||
class="text-xs whitespace-nowrap font-semibold opacity-60 capitalize"
|
class="text-xs whitespace-nowrap font-semibold opacity-60 capitalize"
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
>
|
>
|
||||||
|
|
||||||
<td class="w-fit overflow-x-auto whitespace-nowrap">
|
<td class="w-fit overflow-x-auto whitespace-nowrap">
|
||||||
{#if key.toLowerCase() === "url"}
|
{#if key.toLowerCase() == "url" || key.toLowerCase().endsWith("_url")}
|
||||||
<a
|
<a
|
||||||
href={value}
|
href={value}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
type Query = {
|
export type Query = {
|
||||||
Text: string;
|
Text: string;
|
||||||
Column: string;
|
Column: string;
|
||||||
ExactMatch: boolean;
|
ExactMatch: boolean;
|
||||||
@@ -6,33 +6,45 @@ type Query = {
|
|||||||
// Services
|
// Services
|
||||||
Datawells: boolean;
|
Datawells: boolean;
|
||||||
GithubRecon: boolean;
|
GithubRecon: boolean;
|
||||||
|
GravatarRecon: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type LeakResult = {
|
export type LeakResult = {
|
||||||
Duration: number;
|
Duration: number;
|
||||||
Error: string;
|
Error: string;
|
||||||
Rows: Array<Record<string, string>>;
|
Rows: Array<Record<string, string>>;
|
||||||
LimitHit: boolean;
|
LimitHit: boolean;
|
||||||
|
Inactive: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type GithubResult = {
|
export type GithubResult = {
|
||||||
Duration: number;
|
Duration: number;
|
||||||
Error: string;
|
Error: string;
|
||||||
|
Inactive: boolean;
|
||||||
|
|
||||||
EmailResult: any;
|
EmailResult: any;
|
||||||
UsernameResult: any;
|
UsernameResult: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Result = {
|
export type GravatarResult = {
|
||||||
|
Duration: number;
|
||||||
|
Error: string;
|
||||||
|
Inactive: boolean;
|
||||||
|
|
||||||
|
Results: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Result = {
|
||||||
Id: string;
|
Id: string;
|
||||||
Status: "pending" | "completed";
|
Status: "pending" | "completed";
|
||||||
Date: string;
|
Date: string;
|
||||||
Query: Query;
|
Query: Query;
|
||||||
LeakResult: LeakResult;
|
LeakResult: LeakResult;
|
||||||
GithubResult: GithubResult;
|
GithubResult: GithubResult;
|
||||||
|
GravatarResult: GravatarResult;
|
||||||
};
|
};
|
||||||
|
|
||||||
type HistoryItem = {
|
export type HistoryItem = {
|
||||||
Id: string;
|
Id: string;
|
||||||
Status: "pending" | "completed";
|
Status: "pending" | "completed";
|
||||||
Date: string;
|
Date: string;
|
||||||
@@ -40,9 +52,9 @@ type HistoryItem = {
|
|||||||
Results: number;
|
Results: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type History = HistoryItem[];
|
export type History = HistoryItem[];
|
||||||
|
|
||||||
type ServerSettings = {
|
export type ServerSettings = {
|
||||||
Folders: string[];
|
Folders: string[];
|
||||||
CacheFolder: string;
|
CacheFolder: string;
|
||||||
Limit: number;
|
Limit: number;
|
||||||
@@ -51,9 +63,10 @@ type ServerSettings = {
|
|||||||
GithubRecon: boolean;
|
GithubRecon: boolean;
|
||||||
GithubTokenLoaded: boolean;
|
GithubTokenLoaded: boolean;
|
||||||
GithubDeepMode: boolean;
|
GithubDeepMode: boolean;
|
||||||
|
GravatarRecon: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Server = {
|
export type Server = {
|
||||||
Settings: ServerSettings;
|
Settings: ServerSettings;
|
||||||
|
|
||||||
Dataleaks: Dataleak[];
|
Dataleaks: Dataleak[];
|
||||||
@@ -63,21 +76,9 @@ type Server = {
|
|||||||
TotalSize: number;
|
TotalSize: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Dataleak = {
|
export type Dataleak = {
|
||||||
Name: string;
|
Name: string;
|
||||||
Columns: string[];
|
Columns: string[];
|
||||||
Length: number;
|
Length: number;
|
||||||
Size: number;
|
Size: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type {
|
|
||||||
Query,
|
|
||||||
LeakResult,
|
|
||||||
History,
|
|
||||||
HistoryItem,
|
|
||||||
GithubResult,
|
|
||||||
Result,
|
|
||||||
ServerSettings,
|
|
||||||
Server,
|
|
||||||
Dataleak,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
} from "@lucide/svelte";
|
} from "@lucide/svelte";
|
||||||
import { convertNanoSeconds } from "$src/lib/utils";
|
import { convertNanoSeconds } from "$src/lib/utils";
|
||||||
import GithubResult from "$src/lib/components/index/search/id/githubResult.svelte";
|
import GithubResult from "$src/lib/components/index/search/id/githubResult.svelte";
|
||||||
|
import GravatarResult from "$src/lib/components/index/search/id/gravatarResult.svelte";
|
||||||
|
|
||||||
route.getParams("/search/:id");
|
route.getParams("/search/:id");
|
||||||
|
|
||||||
@@ -41,6 +42,7 @@
|
|||||||
})
|
})
|
||||||
.then((r) => {
|
.then((r) => {
|
||||||
result = r.data;
|
result = r.data;
|
||||||
|
console.log(result);
|
||||||
if (result && result.Status !== "pending") {
|
if (result && result.Status !== "pending") {
|
||||||
clearInterval(intervalId);
|
clearInterval(intervalId);
|
||||||
}
|
}
|
||||||
@@ -99,8 +101,8 @@
|
|||||||
<main>
|
<main>
|
||||||
{#if result}
|
{#if result}
|
||||||
<header class="flex gap-5 flex-col">
|
<header class="flex gap-5 flex-col">
|
||||||
<a href="/search">
|
<a href="/search" class="w-fit">
|
||||||
<h1 class="h1"><span class="text-2xl align-middle">🔍</span> Search</h1>
|
<h1 class="h1 "><span class="text-2xl align-middle">🔍</span> Search</h1>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<Searchbar
|
<Searchbar
|
||||||
@@ -119,7 +121,7 @@
|
|||||||
<Stats {result} />
|
<Stats {result} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if result.LeakResult.Error !== "not enabled"}
|
{#if !result.LeakResult.Inactive}
|
||||||
<div class="collapse collapse-arrow bg-base-100 border">
|
<div class="collapse collapse-arrow bg-base-100 border">
|
||||||
<input type="radio" name="my-accordion-2" checked={true} />
|
<input type="radio" name="my-accordion-2" checked={true} />
|
||||||
<div
|
<div
|
||||||
@@ -166,6 +168,11 @@
|
|||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
{:else if !result.LeakResult.Rows || result.LeakResult.Rows.length == 0}
|
||||||
|
<div role="alert" class="alert alert-soft">
|
||||||
|
<CircleMinus size={20} />
|
||||||
|
<span>No result</span>
|
||||||
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<p class="text-base-content/60">
|
<p class="text-base-content/60">
|
||||||
{result.LeakResult.Rows.length} results in {convertNanoSeconds(
|
{result.LeakResult.Rows.length} results in {convertNanoSeconds(
|
||||||
@@ -186,7 +193,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if result.GithubResult.Error !== "not enabled"}
|
{#if !result.GithubResult.Inactive}
|
||||||
<div class="collapse collapse-arrow bg-base-100 border">
|
<div class="collapse collapse-arrow bg-base-100 border">
|
||||||
<input type="radio" name="my-accordion-2" />
|
<input type="radio" name="my-accordion-2" />
|
||||||
<div
|
<div
|
||||||
@@ -233,6 +240,64 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if !result.GravatarResult.Inactive}
|
||||||
|
<div class="collapse collapse-arrow bg-base-100 border">
|
||||||
|
<input type="radio" name="my-accordion-2" />
|
||||||
|
<div
|
||||||
|
class="collapse-title font-semibold text-xl flex justify-between items-center"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<svg
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
viewBox="0 0 18 18"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M7.20008 1.79933V8.09932C7.20008 8.57653 7.38965 9.0342 7.72709 9.37164C8.06453 9.70908 8.5222 9.89865 8.99941 9.89865C9.47662 9.89865 9.93429 9.70908 10.2717 9.37164C10.6092 9.0342 10.7987 8.57653 10.7987 8.09932V3.90799C11.9031 4.29735 12.851 5.03509 13.4996 6.01006C14.1482 6.98502 14.4623 8.14438 14.3947 9.31342C14.327 10.4825 13.8812 11.5978 13.1245 12.4915C12.3678 13.3851 11.3411 14.0086 10.1992 14.2679C9.05725 14.5273 7.86198 14.4084 6.79347 13.9294C5.72497 13.4503 4.84112 12.6369 4.27513 11.6117C3.70914 10.5866 3.49168 9.40529 3.6555 8.24581C3.81933 7.08634 4.35557 6.01152 5.18342 5.18333C5.51545 4.84434 5.70032 4.38803 5.69786 3.91353C5.69541 3.43902 5.50582 2.98465 5.17029 2.64912C4.83476 2.31359 4.38039 2.12401 3.90589 2.12155C3.43138 2.11909 2.97508 2.30396 2.63609 2.636C1.16373 4.10834 0.247437 6.04566 0.043349 8.11786C-0.160739 10.1901 0.360003 12.2689 1.51684 14.0002C2.67368 15.7315 4.39505 17.0081 6.38762 17.6125C8.38019 18.2169 10.5207 18.1117 12.4444 17.3148C14.3681 16.5179 15.956 15.0786 16.9374 13.2422C17.9189 11.4059 18.2333 9.28595 17.827 7.24376C17.4207 5.20156 16.3188 3.36344 14.7091 2.04258C13.0995 0.721724 11.0816 -0.000136192 8.99941 1.92733e-08C8.5222 1.92733e-08 8.06453 0.189572 7.72709 0.527012C7.38965 0.864452 7.20008 1.32212 7.20008 1.79933Z"
|
||||||
|
class="fill-base-content/60"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
Gravatar Recon
|
||||||
|
</div>
|
||||||
|
{#if result.GravatarResult.Error !== ""}
|
||||||
|
<CircleX size={16} class="text-error" />
|
||||||
|
{:else if result.GravatarResult.Duration === 0}
|
||||||
|
<span class="loading loading-dots loading-xs"></span>
|
||||||
|
{:else if !result.GravatarResult.Results || result.GravatarResult.Results.length == 0}
|
||||||
|
<CircleMinus size={16} class="text-base-content/60" />
|
||||||
|
{:else if result.GravatarResult.Results}
|
||||||
|
<CircleCheck size={16} class="text-success" />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="collapse-content">
|
||||||
|
{#if result.GravatarResult.Error !== ""}
|
||||||
|
<div role="alert" class="alert alert-soft alert-error">
|
||||||
|
<CircleAlert size={20} />
|
||||||
|
<span>Error! {result.GravatarResult.Error}</span>
|
||||||
|
</div>
|
||||||
|
{:else if result.GravatarResult.Duration === 0}
|
||||||
|
<div role="alert" class="alert alert-soft">
|
||||||
|
<span class="loading loading-dots loading-sm"></span>
|
||||||
|
<span>Loading...</span>
|
||||||
|
</div>
|
||||||
|
{:else if !result.GravatarResult.Results || result.GravatarResult.Results.length == 0}
|
||||||
|
<div role="alert" class="alert alert-soft">
|
||||||
|
<CircleMinus size={20} />
|
||||||
|
<span>No result</span>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<p class="text-base-content/60 mb-4">
|
||||||
|
Found a result in {convertNanoSeconds(
|
||||||
|
result.GravatarResult.Duration,
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<GravatarResult result={result.GravatarResult} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
10
nix/back.nix
10
nix/back.nix
@@ -99,6 +99,11 @@ in {
|
|||||||
default = false;
|
default = false;
|
||||||
description = "Activate the github-recon deep mode";
|
description = "Activate the github-recon deep mode";
|
||||||
};
|
};
|
||||||
|
gravatarRecon = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Activate the gravatar-recon OSINT module";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
@@ -147,6 +152,11 @@ in {
|
|||||||
then "true"
|
then "true"
|
||||||
else "false"
|
else "false"
|
||||||
}"
|
}"
|
||||||
|
"GRAVATAR_RECON=${
|
||||||
|
if cfg.gravatarRecon
|
||||||
|
then "true"
|
||||||
|
else "false"
|
||||||
|
}"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user