mirror of
https://github.com/luxfi/broker.git
synced 2026-09-24 00:25:18 +00:00
- SSN hashing: upgrade from plain SHA-256 to HMAC-SHA256 with keyed secret (SSN_HMAC_KEY env var from KMS) to prevent rainbow table attacks on the small 10^9 SSN keyspace - Document uploads: validate MIME type (pdf/jpeg/png/tiff only), enforce 10MB size limit, validate base64 encoding - Error messages: sanitize all handler error responses to prevent leaking internal store errors (PostgreSQL query details, path info) to clients - Rate limiting: add per-IP rate limits (10/min) on KYC verify, AML screen, and AML risk-assessment endpoints - Security headers: add X-Content-Type-Options, X-Frame-Options, Cache-Control to all compliance responses
187 lines
5.1 KiB
Go
187 lines
5.1 KiB
Go
package compliance
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
)
|
|
|
|
// onboardingHandler holds pipeline/session HTTP handler state.
|
|
type onboardingHandler struct {
|
|
store ComplianceStore
|
|
}
|
|
|
|
// --- Pipeline Handlers ---
|
|
|
|
func (h *onboardingHandler) handleListPipelines(w http.ResponseWriter, r *http.Request) {
|
|
writeJSON(w, http.StatusOK, h.store.ListPipelines())
|
|
}
|
|
|
|
func (h *onboardingHandler) handleCreatePipeline(w http.ResponseWriter, r *http.Request) {
|
|
var p Pipeline
|
|
if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid request body")
|
|
return
|
|
}
|
|
if p.Name == "" {
|
|
writeError(w, http.StatusBadRequest, "name is required")
|
|
return
|
|
}
|
|
if p.Status == "" {
|
|
p.Status = "draft"
|
|
}
|
|
if err := h.store.SavePipeline(&p); err != nil {
|
|
writeError(w, http.StatusInternalServerError, "internal error")
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusCreated, &p)
|
|
}
|
|
|
|
func (h *onboardingHandler) handleGetPipeline(w http.ResponseWriter, r *http.Request) {
|
|
p, err := h.store.GetPipeline(chi.URLParam(r, "id"))
|
|
if err != nil {
|
|
writeError(w, http.StatusNotFound, "not found")
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, p)
|
|
}
|
|
|
|
func (h *onboardingHandler) handleUpdatePipeline(w http.ResponseWriter, r *http.Request) {
|
|
id := chi.URLParam(r, "id")
|
|
existing, err := h.store.GetPipeline(id)
|
|
if err != nil {
|
|
writeError(w, http.StatusNotFound, "not found")
|
|
return
|
|
}
|
|
|
|
var patch struct {
|
|
Name *string `json:"name,omitempty"`
|
|
Steps []PipelineStep `json:"steps,omitempty"`
|
|
Status *string `json:"status,omitempty"`
|
|
BusinessID *string `json:"business_id,omitempty"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&patch); err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid request body")
|
|
return
|
|
}
|
|
if patch.Name != nil {
|
|
existing.Name = *patch.Name
|
|
}
|
|
if patch.Steps != nil {
|
|
existing.Steps = patch.Steps
|
|
}
|
|
if patch.Status != nil {
|
|
existing.Status = *patch.Status
|
|
}
|
|
if patch.BusinessID != nil {
|
|
existing.BusinessID = *patch.BusinessID
|
|
}
|
|
if err := h.store.SavePipeline(existing); err != nil {
|
|
writeError(w, http.StatusInternalServerError, "internal error")
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, existing)
|
|
}
|
|
|
|
func (h *onboardingHandler) handleDeletePipeline(w http.ResponseWriter, r *http.Request) {
|
|
if err := h.store.DeletePipeline(chi.URLParam(r, "id")); err != nil {
|
|
writeError(w, http.StatusNotFound, "not found")
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]string{"status": "deleted"})
|
|
}
|
|
|
|
// --- Session Handlers ---
|
|
|
|
func (h *onboardingHandler) handleListSessions(w http.ResponseWriter, r *http.Request) {
|
|
writeJSON(w, http.StatusOK, h.store.ListSessions())
|
|
}
|
|
|
|
func (h *onboardingHandler) handleCreateSession(w http.ResponseWriter, r *http.Request) {
|
|
var sess Session
|
|
if err := json.NewDecoder(r.Body).Decode(&sess); err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid request body")
|
|
return
|
|
}
|
|
if sess.PipelineID == "" {
|
|
writeError(w, http.StatusBadRequest, "pipeline_id is required")
|
|
return
|
|
}
|
|
if sess.InvestorEmail == "" {
|
|
writeError(w, http.StatusBadRequest, "investor_email is required")
|
|
return
|
|
}
|
|
|
|
// Validate pipeline exists and populate session steps from it.
|
|
pipeline, err := h.store.GetPipeline(sess.PipelineID)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "pipeline not found")
|
|
return
|
|
}
|
|
sess.Status = SessionPending
|
|
sess.KYCStatus = KYCPending
|
|
sess.Steps = make([]SessionStep, len(pipeline.Steps))
|
|
for i, ps := range pipeline.Steps {
|
|
sess.Steps[i] = SessionStep{
|
|
StepID: ps.ID,
|
|
Name: ps.Name,
|
|
Type: ps.Type,
|
|
Status: "pending",
|
|
}
|
|
}
|
|
|
|
if err := h.store.SaveSession(&sess); err != nil {
|
|
writeError(w, http.StatusInternalServerError, "internal error")
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusCreated, &sess)
|
|
}
|
|
|
|
func (h *onboardingHandler) handleGetSession(w http.ResponseWriter, r *http.Request) {
|
|
sess, err := h.store.GetSession(chi.URLParam(r, "id"))
|
|
if err != nil {
|
|
writeError(w, http.StatusNotFound, "not found")
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, sess)
|
|
}
|
|
|
|
func (h *onboardingHandler) handleUpdateSession(w http.ResponseWriter, r *http.Request) {
|
|
id := chi.URLParam(r, "id")
|
|
existing, err := h.store.GetSession(id)
|
|
if err != nil {
|
|
writeError(w, http.StatusNotFound, "not found")
|
|
return
|
|
}
|
|
|
|
var patch struct {
|
|
Status *SessionStatus `json:"status,omitempty"`
|
|
KYCStatus *KYCStatus `json:"kyc_status,omitempty"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&patch); err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid request body")
|
|
return
|
|
}
|
|
if patch.Status != nil {
|
|
existing.Status = *patch.Status
|
|
}
|
|
if patch.KYCStatus != nil {
|
|
existing.KYCStatus = *patch.KYCStatus
|
|
}
|
|
if err := h.store.SaveSession(existing); err != nil {
|
|
writeError(w, http.StatusInternalServerError, "internal error")
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, existing)
|
|
}
|
|
|
|
func (h *onboardingHandler) handleGetSessionSteps(w http.ResponseWriter, r *http.Request) {
|
|
sess, err := h.store.GetSession(chi.URLParam(r, "id"))
|
|
if err != nil {
|
|
writeError(w, http.StatusNotFound, "not found")
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, sess.Steps)
|
|
}
|