chore: ARTEX 初始化
ARTEX — AI 自主渗透测试系统(Go 后端 + Next.js 前端,单二进制内嵌)。 Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
commit
0ef8561411
287 files changed
+70178
No files matched your search
@@ -0,0 +1,426 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/Autumn-27/artex/db"
|
||||
)
|
||||
|
||||
// assetStore returns the asset store.
|
||||
func (s *Server) assetStore() *db.AssetStore {
|
||||
if s.m.pg == nil {
|
||||
return nil
|
||||
}
|
||||
return s.m.pg.Assets()
|
||||
}
|
||||
|
||||
// companyStore returns the company store.
|
||||
func (s *Server) companyStore() *db.CompanyStore {
|
||||
if s.m.pg == nil {
|
||||
return nil
|
||||
}
|
||||
return s.m.pg.Companies()
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// GET /api/companies
|
||||
// =====================================================================
|
||||
|
||||
func (s *Server) listCompanies(w http.ResponseWriter, r *http.Request) {
|
||||
cs := s.companyStore()
|
||||
if cs == nil {
|
||||
writeErr(w, 503, "database unavailable")
|
||||
return
|
||||
}
|
||||
companies, err := cs.ListCompanies()
|
||||
if err != nil {
|
||||
writeErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, 200, companies)
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// POST /api/companies
|
||||
// =====================================================================
|
||||
|
||||
func (s *Server) createCompany(w http.ResponseWriter, r *http.Request) {
|
||||
cs := s.companyStore()
|
||||
if cs == nil {
|
||||
writeErr(w, 503, "database unavailable")
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
Logo string `json:"logo"`
|
||||
Scope []string `json:"scope"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeErr(w, 400, "invalid JSON: "+err.Error())
|
||||
return
|
||||
}
|
||||
if req.Name == "" {
|
||||
writeErr(w, 400, "name required")
|
||||
return
|
||||
}
|
||||
id, created, err := cs.UpsertCompany(req.Name, req.Logo)
|
||||
if err != nil {
|
||||
writeErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
out := map[string]any{"id": id, "created": created}
|
||||
if len(req.Scope) > 0 {
|
||||
added, skipped, invalid, errs := cs.AddScope(id, req.Scope, "api")
|
||||
out["scope_added"] = added
|
||||
out["scope_skipped"] = skipped
|
||||
out["scope_invalid"] = invalid
|
||||
if len(errs) > 0 {
|
||||
out["scope_errors"] = errs
|
||||
}
|
||||
}
|
||||
writeJSON(w, 201, out)
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// GET /api/companies/{id}
|
||||
// =====================================================================
|
||||
|
||||
func (s *Server) getCompany(w http.ResponseWriter, r *http.Request) {
|
||||
cs := s.companyStore()
|
||||
if cs == nil {
|
||||
writeErr(w, 503, "database unavailable")
|
||||
return
|
||||
}
|
||||
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
||||
if err != nil {
|
||||
writeErr(w, 400, "invalid id")
|
||||
return
|
||||
}
|
||||
c, err := cs.GetCompany(id)
|
||||
if err != nil {
|
||||
writeErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
if c == nil {
|
||||
writeErr(w, 404, "company not found")
|
||||
return
|
||||
}
|
||||
scope, err := cs.GetScope(id)
|
||||
if err != nil {
|
||||
writeErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, 200, map[string]any{"company": c, "scope": scope})
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// POST /api/companies/{id}/scope
|
||||
// =====================================================================
|
||||
|
||||
func (s *Server) addCompanyScope(w http.ResponseWriter, r *http.Request) {
|
||||
cs := s.companyStore()
|
||||
if cs == nil {
|
||||
writeErr(w, 503, "database unavailable")
|
||||
return
|
||||
}
|
||||
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
||||
if err != nil {
|
||||
writeErr(w, 400, "invalid company id")
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
Scope []string `json:"scope"`
|
||||
Reason string `json:"reason"`
|
||||
Reset bool `json:"reset"` // if true, replace existing scope
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeErr(w, 400, "invalid JSON: "+err.Error())
|
||||
return
|
||||
}
|
||||
var added, skipped, invalid int
|
||||
var errs []string
|
||||
if req.Reset {
|
||||
added, invalid, errs = cs.UpdateScope(id, req.Scope, req.Reason)
|
||||
} else {
|
||||
added, skipped, invalid, errs = cs.AddScope(id, req.Scope, req.Reason)
|
||||
}
|
||||
out := map[string]any{
|
||||
"added": added,
|
||||
"skipped": skipped,
|
||||
"invalid": invalid,
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
out["errors"] = errs
|
||||
}
|
||||
writeJSON(w, 200, out)
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// DELETE /api/companies/{id}
|
||||
// =====================================================================
|
||||
|
||||
func (s *Server) deleteCompany(w http.ResponseWriter, r *http.Request) {
|
||||
cs := s.companyStore()
|
||||
if cs == nil {
|
||||
writeErr(w, 503, "database unavailable")
|
||||
return
|
||||
}
|
||||
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
||||
if err != nil {
|
||||
writeErr(w, 400, "invalid id")
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
DeleteAssets bool `json:"delete_assets"`
|
||||
}
|
||||
// Body is optional; ignore decode errors (e.g. empty body).
|
||||
_ = json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
assetsDeleted := int64(0)
|
||||
if req.DeleteAssets {
|
||||
as := s.assetStore()
|
||||
if as != nil {
|
||||
assetsDeleted, err = as.DeleteByCompanyID(id)
|
||||
if err != nil {
|
||||
writeErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := cs.DeleteCompany(id); err != nil {
|
||||
writeErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, 200, map[string]any{"deleted": 1, "assets_deleted": assetsDeleted})
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// POST /api/companies/reattribute
|
||||
// =====================================================================
|
||||
|
||||
func (s *Server) reattribute(w http.ResponseWriter, r *http.Request) {
|
||||
cs := s.companyStore()
|
||||
if cs == nil {
|
||||
writeErr(w, 503, "database unavailable")
|
||||
return
|
||||
}
|
||||
if err := cs.RecomputeAttribution(); err != nil {
|
||||
writeErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, 200, map[string]any{"ok": true})
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// GET /api/assets
|
||||
// =====================================================================
|
||||
|
||||
func (s *Server) listAssets(w http.ResponseWriter, r *http.Request) {
|
||||
as := s.assetStore()
|
||||
if as == nil {
|
||||
writeErr(w, 503, "database unavailable")
|
||||
return
|
||||
}
|
||||
q := r.URL.Query()
|
||||
typ := q.Get("type")
|
||||
limit, _ := strconv.Atoi(q.Get("limit"))
|
||||
offset, _ := strconv.Atoi(q.Get("offset"))
|
||||
if limit <= 0 {
|
||||
limit = 50
|
||||
}
|
||||
|
||||
var assets []*db.Asset
|
||||
var err error
|
||||
// total is the full match count ignoring limit/offset — lets the UI paginate on
|
||||
// the server. For company/task scopes it falls back to the returned length.
|
||||
total := 0
|
||||
|
||||
if dsl := q.Get("dsl"); dsl != "" {
|
||||
assets, err = as.QueryDSL(dsl, typ, limit, offset)
|
||||
if err == nil {
|
||||
total, _ = as.CountDSL(dsl, typ)
|
||||
}
|
||||
} else {
|
||||
companyID, _ := strconv.ParseInt(q.Get("company_id"), 10, 64)
|
||||
taskID, _ := strconv.ParseInt(q.Get("task_id"), 10, 64)
|
||||
switch {
|
||||
case companyID > 0:
|
||||
assets, err = as.QueryByCompany(companyID, typ, limit)
|
||||
total = len(assets)
|
||||
case taskID > 0:
|
||||
assets, err = as.QueryByTask(taskID, typ, limit)
|
||||
total = len(assets)
|
||||
default:
|
||||
if typ == "" {
|
||||
typ = "root_domain"
|
||||
}
|
||||
assets, err = as.QueryByType(typ, limit, offset)
|
||||
if err == nil {
|
||||
total, _ = as.CountByType(typ)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
writeErr(w, 400, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, 200, map[string]any{
|
||||
"count": len(assets),
|
||||
"total": total,
|
||||
"assets": assets,
|
||||
})
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// GET /api/assets/counts
|
||||
// =====================================================================
|
||||
|
||||
func (s *Server) assetCounts(w http.ResponseWriter, r *http.Request) {
|
||||
as := s.assetStore()
|
||||
if as == nil {
|
||||
writeErr(w, 503, "database unavailable")
|
||||
return
|
||||
}
|
||||
counts, err := as.CountsByType()
|
||||
if err != nil {
|
||||
writeErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, 200, counts)
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// DELETE /api/assets
|
||||
// =====================================================================
|
||||
|
||||
func (s *Server) deleteAssets(w http.ResponseWriter, r *http.Request) {
|
||||
as := s.assetStore()
|
||||
if as == nil {
|
||||
writeErr(w, 503, "database unavailable")
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
IDs []int64 `json:"ids"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeErr(w, 400, "invalid JSON: "+err.Error())
|
||||
return
|
||||
}
|
||||
if len(req.IDs) == 0 {
|
||||
writeErr(w, 400, "ids required")
|
||||
return
|
||||
}
|
||||
deleted, err := as.DeleteByIDs(req.IDs)
|
||||
if err != nil {
|
||||
writeErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, 200, map[string]any{"deleted": deleted})
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// POST /api/assets
|
||||
// =====================================================================
|
||||
|
||||
func (s *Server) insertAssets(w http.ResponseWriter, r *http.Request) {
|
||||
as := s.assetStore()
|
||||
if as == nil {
|
||||
writeErr(w, 503, "database unavailable")
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
TaskID int64 `json:"task_id"`
|
||||
Assets []struct {
|
||||
Type string `json:"type"`
|
||||
// root_domain / subdomain
|
||||
Domain string `json:"domain"`
|
||||
ICP string `json:"icp"`
|
||||
RecordType string `json:"record_type"`
|
||||
RecordValue []string `json:"record_value"`
|
||||
// ip
|
||||
IP string `json:"ip"`
|
||||
BoundDomains []string `json:"bound_domains"`
|
||||
OpenPorts []db.PortService `json:"open_ports"`
|
||||
// app
|
||||
AppName string `json:"app_name"`
|
||||
BundleID string `json:"bundle_id"`
|
||||
Category string `json:"category"`
|
||||
Description string `json:"description"`
|
||||
AppICP string `json:"app_icp"`
|
||||
// service http
|
||||
URL string `json:"url"`
|
||||
Technologies []string `json:"technologies"`
|
||||
StatusCode *int `json:"status_code"`
|
||||
ContentLength *int64 `json:"content_length"`
|
||||
PageTitle string `json:"page_title"`
|
||||
FaviconMMH3 string `json:"favicon_mmh3"`
|
||||
Auth []map[string]any `json:"auth"`
|
||||
ServiceIP string `json:"service_ip"`
|
||||
// service other
|
||||
Port int `json:"port"`
|
||||
ServiceName string `json:"service_name"`
|
||||
// endpoint
|
||||
Method string `json:"method"`
|
||||
Params []map[string]any `json:"params"`
|
||||
} `json:"assets"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeErr(w, 400, "invalid JSON: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
type result struct {
|
||||
Index int `json:"index"`
|
||||
ID int64 `json:"id"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
type errEntry struct {
|
||||
Index int `json:"index"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
var results []result
|
||||
var errs []errEntry
|
||||
|
||||
for i, a := range req.Assets {
|
||||
var id int64
|
||||
var err error
|
||||
switch a.Type {
|
||||
case "root_domain":
|
||||
id, err = as.UpsertRootDomain(db.UpsertRootDomainReq{Domain: a.Domain, ICP: a.ICP, TaskID: req.TaskID})
|
||||
case "ip":
|
||||
id, err = as.UpsertIP(db.UpsertIPReq{IP: a.IP, BoundDomains: a.BoundDomains, OpenPorts: a.OpenPorts, TaskID: req.TaskID})
|
||||
case "subdomain":
|
||||
id, err = as.UpsertSubdomain(db.UpsertSubdomainReq{Domain: a.Domain, RecordType: a.RecordType, RecordValue: a.RecordValue, ICP: a.ICP, TaskID: req.TaskID})
|
||||
case "app":
|
||||
id, err = as.UpsertApp(db.UpsertAppReq{Name: a.AppName, BundleID: a.BundleID, Category: a.Category, Description: a.Description, ICP: a.AppICP, TaskID: req.TaskID})
|
||||
case "service":
|
||||
if a.URL != "" {
|
||||
svcIP := a.ServiceIP
|
||||
if svcIP == "" {
|
||||
svcIP = a.IP
|
||||
}
|
||||
id, err = as.UpsertHTTPService(db.UpsertHTTPServiceReq{URL: a.URL, Technologies: a.Technologies, StatusCode: a.StatusCode, ContentLength: a.ContentLength, PageTitle: a.PageTitle, FaviconMMH3: a.FaviconMMH3, Auth: a.Auth, IP: svcIP, TaskID: req.TaskID})
|
||||
} else {
|
||||
id, err = as.UpsertOtherService(db.UpsertOtherServiceReq{Domain: a.Domain, IP: a.IP, Port: a.Port, ServiceName: a.ServiceName, Auth: a.Auth, TaskID: req.TaskID})
|
||||
}
|
||||
case "endpoint":
|
||||
svcIP := a.ServiceIP
|
||||
if svcIP == "" {
|
||||
svcIP = a.IP
|
||||
}
|
||||
id, err = as.UpsertEndpoint(db.UpsertEndpointReq{URL: a.URL, Method: a.Method, Params: a.Params, IP: svcIP, TaskID: req.TaskID})
|
||||
default:
|
||||
errs = append(errs, errEntry{Index: i, Error: "unknown type: " + a.Type})
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
errs = append(errs, errEntry{Index: i, Error: err.Error()})
|
||||
continue
|
||||
}
|
||||
results = append(results, result{Index: i, ID: id, Type: a.Type})
|
||||
}
|
||||
writeJSON(w, 200, map[string]any{"results": results, "errors": errs})
|
||||
}
|
||||
Reference in new issue
Block a user