mirror of
https://github.com/anotherhadi/iknowyou.git
synced 2026-04-11 16:37:25 +02:00
147 lines
3.8 KiB
Go
147 lines
3.8 KiB
Go
package handler
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"github.com/anotherhadi/iknowyou/internal/respond"
|
|
"github.com/anotherhadi/iknowyou/internal/search"
|
|
"github.com/anotherhadi/iknowyou/internal/tools"
|
|
)
|
|
|
|
type SearchHandler struct {
|
|
manager *search.Manager
|
|
demo bool
|
|
}
|
|
|
|
func NewSearchHandler(manager *search.Manager, demo bool) *SearchHandler {
|
|
return &SearchHandler{manager: manager, demo: demo}
|
|
}
|
|
|
|
type postSearchRequest struct {
|
|
Target string `json:"target"`
|
|
InputType tools.InputType `json:"input_type"`
|
|
Profile string `json:"profile,omitempty"`
|
|
}
|
|
|
|
type searchSummary struct {
|
|
ID string `json:"id"`
|
|
Target string `json:"target"`
|
|
InputType tools.InputType `json:"input_type"`
|
|
Profile string `json:"profile,omitempty"`
|
|
Status search.Status `json:"status"`
|
|
StartedAt string `json:"started_at"`
|
|
PlannedTools []search.ToolStatus `json:"planned_tools"`
|
|
}
|
|
|
|
type searchDetail struct {
|
|
searchSummary
|
|
Events []tools.Event `json:"events"`
|
|
}
|
|
|
|
func toSummary(s *search.Search) searchSummary {
|
|
planned := s.PlannedTools
|
|
if planned == nil {
|
|
planned = []search.ToolStatus{}
|
|
}
|
|
return searchSummary{
|
|
ID: s.ID,
|
|
Target: s.Target,
|
|
InputType: s.InputType,
|
|
Profile: s.Profile,
|
|
Status: s.Status(),
|
|
StartedAt: s.StartedAt.UTC().Format("2006-01-02T15:04:05Z"),
|
|
PlannedTools: planned,
|
|
}
|
|
}
|
|
|
|
var validInputTypes = map[tools.InputType]struct{}{
|
|
tools.InputTypeEmail: {},
|
|
tools.InputTypeUsername: {},
|
|
tools.InputTypePhone: {},
|
|
tools.InputTypeIP: {},
|
|
tools.InputTypeDomain: {},
|
|
tools.InputTypePassword: {},
|
|
tools.InputTypeName: {},
|
|
}
|
|
|
|
// POST /searches
|
|
func (h *SearchHandler) Create(w http.ResponseWriter, r *http.Request) {
|
|
if h.demo {
|
|
respond.Error(w, http.StatusForbidden, "demo mode: searches are disabled")
|
|
return
|
|
}
|
|
var req postSearchRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
respond.Error(w, http.StatusBadRequest, "invalid JSON body")
|
|
return
|
|
}
|
|
if req.Target == "" {
|
|
respond.Error(w, http.StatusBadRequest, "target is required")
|
|
return
|
|
}
|
|
if len(req.Target) > 500 {
|
|
respond.Error(w, http.StatusBadRequest, "target is too long (max 500 characters)")
|
|
return
|
|
}
|
|
if req.Target[0] == '-' || req.Target[0] == '@' {
|
|
respond.Error(w, http.StatusBadRequest, "invalid target")
|
|
return
|
|
}
|
|
if req.InputType == "" {
|
|
respond.Error(w, http.StatusBadRequest, "input_type is required")
|
|
return
|
|
}
|
|
if _, ok := validInputTypes[req.InputType]; !ok {
|
|
respond.Error(w, http.StatusBadRequest, "invalid input_type")
|
|
return
|
|
}
|
|
|
|
s, err := h.manager.Start(context.WithoutCancel(r.Context()), req.Target, req.InputType, req.Profile)
|
|
if err != nil {
|
|
respond.Error(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
respond.JSON(w, http.StatusCreated, toSummary(s))
|
|
}
|
|
|
|
// GET /searches
|
|
func (h *SearchHandler) List(w http.ResponseWriter, r *http.Request) {
|
|
all := h.manager.All()
|
|
summaries := make([]searchSummary, len(all))
|
|
for i, s := range all {
|
|
summaries[i] = toSummary(s)
|
|
}
|
|
respond.JSON(w, http.StatusOK, summaries)
|
|
}
|
|
|
|
// GET /searches/{id}
|
|
func (h *SearchHandler) Get(w http.ResponseWriter, r *http.Request) {
|
|
id := chi.URLParam(r, "id")
|
|
s, err := h.manager.Get(id)
|
|
if err != nil {
|
|
respond.Error(w, http.StatusNotFound, err.Error())
|
|
return
|
|
}
|
|
|
|
detail := searchDetail{
|
|
searchSummary: toSummary(s),
|
|
Events: s.Events(),
|
|
}
|
|
respond.JSON(w, http.StatusOK, detail)
|
|
}
|
|
|
|
// DELETE /searches/{id}
|
|
func (h *SearchHandler) Delete(w http.ResponseWriter, r *http.Request) {
|
|
id := chi.URLParam(r, "id")
|
|
if err := h.manager.Delete(id); err != nil {
|
|
respond.Error(w, http.StatusNotFound, err.Error())
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|