mirror of
https://github.com/BaaRai/ARTEX.git
synced 2026-11-13 11:40:53 +08:00
Task credentials (#3): declare test accounts at task creation, and never lose the accounts the agent registers on its own. - task_credentials table (unique per task+domain+username; upsert overwrites the password in place), idempotent schema migration - POST /api/tasks accepts credentials; GET/POST/DELETE /api/tasks/{id}/credentials serve the UI (manual add, e.g. relaying an OTP-verified account; delete) - empty password = the "agent generates" mode: the entry surfaces in the worker's graph overview with an explicit instruction to register that account with a strong password and record it - new tools save_task_credential / list_task_credentials for worker and pentest; the graph overview injects the task's credentials each round with a standing note: reuse matching entries, always record accounts you register - New Task dialog: optional credentials section (domain, username, password, note) with a dedicated-test-accounts hint - task detail: Credentials tab, passwords masked with explicit reveal/copy, agent-recorded badge, light polling so mid-run recordings appear One-shot binding migration (settings-flag-guarded, same pattern as the other seeds) grants the two tools to worker+pentest on databases seeded before this change; fresh DBs get them via WorkerTools()/the pentest catalog. Closes #3
95 lines
2.6 KiB
Go
95 lines
2.6 KiB
Go
package server
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/Autumn-27/artex/db"
|
|
)
|
|
|
|
// Task credential HTTP handlers (#3). The agent-side write path is the
|
|
// save_task_credential tool; these routes serve the UI (task detail tab) and the
|
|
// creation dialog.
|
|
|
|
// taskCredentialDTO masks nothing — the admin is the credential owner — but the
|
|
// frontend renders passwords masked with an explicit reveal.
|
|
func taskCredentialDTO(c db.TaskCredential) map[string]any {
|
|
return map[string]any{
|
|
"id": c.ID, "task_id": c.TaskID, "domain": c.Domain, "username": c.Username,
|
|
"password": c.Password, "source": c.Source, "note": c.Note,
|
|
"created_at": c.CreatedAt, "updated_at": c.UpdatedAt,
|
|
}
|
|
}
|
|
|
|
func (s *Server) taskCredentialTaskID(w http.ResponseWriter, r *http.Request) (int64, bool) {
|
|
t, ok := s.m.Task(r.PathValue("id"))
|
|
if !ok {
|
|
writeErr(w, 404, "task not found")
|
|
return 0, false
|
|
}
|
|
id, err := strconv.ParseInt(t.ID, 10, 64)
|
|
if err != nil || id <= 0 {
|
|
writeErr(w, 500, "task id is not numeric")
|
|
return 0, false
|
|
}
|
|
return id, true
|
|
}
|
|
|
|
func (s *Server) listTaskCredentials(w http.ResponseWriter, r *http.Request) {
|
|
id, ok := s.taskCredentialTaskID(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
list, err := s.m.PG().TaskCredentials(id)
|
|
if err != nil {
|
|
writeErr(w, 500, err.Error())
|
|
return
|
|
}
|
|
out := make([]map[string]any, 0, len(list))
|
|
for _, c := range list {
|
|
out = append(out, taskCredentialDTO(c))
|
|
}
|
|
writeJSON(w, 200, out)
|
|
}
|
|
|
|
func (s *Server) addTaskCredential(w http.ResponseWriter, r *http.Request) {
|
|
id, ok := s.taskCredentialTaskID(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
var req taskCredentialReq
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeErr(w, 400, "bad json: "+err.Error())
|
|
return
|
|
}
|
|
// source=user: manual adds from the UI (e.g. relaying an OTP-verified account).
|
|
// An empty password stays allowed here — the "agent generates" mode.
|
|
c, err := s.m.PG().UpsertTaskCredential(id, db.TaskCredentialInput{
|
|
Domain: req.Domain, Username: req.Username, Password: req.Password,
|
|
Source: "user", Note: req.Note,
|
|
})
|
|
if err != nil {
|
|
writeErr(w, 400, err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, 201, taskCredentialDTO(c))
|
|
}
|
|
|
|
func (s *Server) deleteTaskCredential(w http.ResponseWriter, r *http.Request) {
|
|
id, ok := s.taskCredentialTaskID(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
cid, err := strconv.ParseInt(r.PathValue("cid"), 10, 64)
|
|
if err != nil || cid <= 0 {
|
|
writeErr(w, 400, "invalid credential id")
|
|
return
|
|
}
|
|
if err := s.m.PG().DeleteTaskCredential(id, cid); err != nil {
|
|
writeErr(w, 404, err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, 200, map[string]any{"ok": true})
|
|
}
|