78b57273e2
- Add password hashing with bcrypt - Add AuthService with proper login - Add password strength validation - Add RBAC middleware (AdminOnly, ManagerOrAdmin) - Add tenant isolation middleware - Update CRM handler with tenant filtering - Add JWT fallback for development mode - Add user context helpers - Build successful
286 lines
8.5 KiB
Go
286 lines
8.5 KiB
Go
package handlers
|
|
|
|
import (
|
|
"database/sql"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
// ComplianceHandler hanterar ISO, GDPR, risk och full legal compliance
|
|
type ComplianceHandler struct {
|
|
DB *sql.DB
|
|
}
|
|
|
|
func NewComplianceHandler(db *sql.DB) *ComplianceHandler {
|
|
return &ComplianceHandler{DB: db}
|
|
}
|
|
|
|
// ISOCertification representerar en ISO-certifiering
|
|
type ISOCertification struct {
|
|
ID string `json:"id"`
|
|
Standard string `json:"standard"`
|
|
Name string `json:"name"`
|
|
Status string `json:"status"`
|
|
IssuedAt time.Time `json:"issued_at"`
|
|
ExpiresAt time.Time `json:"expires_at"`
|
|
Issuer string `json:"issuer"`
|
|
Scope string `json:"scope"`
|
|
EntityID string `json:"entity_id"`
|
|
Auditor string `json:"auditor"`
|
|
LastAudit time.Time `json:"last_audit"`
|
|
NextAudit time.Time `json:"next_audit"`
|
|
Findings int `json:"findings"`
|
|
MajorFindings int `json:"major_findings"`
|
|
}
|
|
|
|
// GDPRRecord representerar en GDPR/behandlingsregister-post
|
|
type GDPRRecord struct {
|
|
ID string `json:"id"`
|
|
EntityID string `json:"entity_id"`
|
|
Purpose string `json:"purpose"`
|
|
DataSubjects []string `json:"data_subjects"`
|
|
DataTypes []string `json:"data_types"`
|
|
LegalBasis string `json:"legal_basis"`
|
|
Retention string `json:"retention"`
|
|
Processors []string `json:"processors"`
|
|
DPAExists bool `json:"dpa_exists"`
|
|
CrossBorder bool `json:"cross_border"`
|
|
ImpactAssessment bool `json:"impact_assessment"`
|
|
LastReview string `json:"last_review"`
|
|
}
|
|
|
|
// RiskEntry representerar en risk
|
|
type RiskEntry struct {
|
|
ID string `json:"id"`
|
|
EntityID string `json:"entity_id"`
|
|
Category string `json:"category"`
|
|
Description string `json:"description"`
|
|
Probability int `json:"probability"`
|
|
Impact int `json:"impact"`
|
|
Score int `json:"score"`
|
|
Mitigation string `json:"mitigation"`
|
|
Owner string `json:"owner"`
|
|
Status string `json:"status"`
|
|
ReviewDate time.Time `json:"review_date"`
|
|
}
|
|
|
|
// LegalCase representerar ett juridiskt ärende
|
|
type LegalCase struct {
|
|
ID string `json:"id"`
|
|
EntityID string `json:"entity_id"`
|
|
Title string `json:"title"`
|
|
Type string `json:"type"`
|
|
Status string `json:"status"`
|
|
Priority string `json:"priority"`
|
|
Description string `json:"description"`
|
|
OpposingParty string `json:"opposing_party"`
|
|
Lawyer string `json:"lawyer"`
|
|
OpenedAt time.Time `json:"opened_at"`
|
|
ClosedAt *time.Time `json:"closed_at,omitempty"`
|
|
Value float64 `json:"value"`
|
|
Currency string `json:"currency"`
|
|
}
|
|
|
|
// Policy representerar en policy
|
|
type Policy struct {
|
|
ID string `json:"id"`
|
|
Title string `json:"title"`
|
|
Category string `json:"category"`
|
|
Version string `json:"version"`
|
|
Status string `json:"status"`
|
|
ApprovedBy string `json:"approved_by"`
|
|
ApprovedAt time.Time `json:"approved_at"`
|
|
ReviewDate time.Time `json:"review_date"`
|
|
EntityID string `json:"entity_id"`
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
// GetISO returnerar alla ISO-certifieringar
|
|
func (h *ComplianceHandler) GetISO(w http.ResponseWriter, r *http.Request) {
|
|
certs := []ISOCertification{
|
|
{
|
|
ID: "iso-27001-001",
|
|
Standard: "ISO/IEC 27001:2022",
|
|
Name: "Information Security Management",
|
|
Status: "active",
|
|
IssuedAt: time.Now().Add(-180 * 24 * time.Hour),
|
|
ExpiresAt: time.Now().Add(185 * 24 * time.Hour),
|
|
Issuer: "Bureau Veritas",
|
|
Scope: "All AMOS cloud infrastructure and data processing",
|
|
EntityID: "lvx-ab",
|
|
Auditor: "Anna Lindgren",
|
|
LastAudit: time.Now().Add(-30 * 24 * time.Hour),
|
|
NextAudit: time.Now().Add(60 * 24 * time.Hour),
|
|
Findings: 2,
|
|
MajorFindings: 0,
|
|
},
|
|
{
|
|
ID: "iso-9001-001",
|
|
Standard: "ISO 9001:2015",
|
|
Name: "Quality Management",
|
|
Status: "active",
|
|
IssuedAt: time.Now().Add(-365 * 24 * time.Hour),
|
|
ExpiresAt: time.Now().Add(365 * 24 * time.Hour),
|
|
Issuer: "SGS",
|
|
Scope: "AI model development and deployment processes",
|
|
EntityID: "lvx-ab",
|
|
Auditor: "Marcus Berg",
|
|
LastAudit: time.Now().Add(-60 * 24 * time.Hour),
|
|
NextAudit: time.Now().Add(120 * 24 * time.Hour),
|
|
Findings: 0,
|
|
MajorFindings: 0,
|
|
},
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"ok": true,
|
|
"certifications": certs,
|
|
"summary": map[string]interface{}{
|
|
"total": len(certs),
|
|
"active": 2,
|
|
"in_progress": 1,
|
|
"planned": 1,
|
|
"expiring_soon": 0,
|
|
},
|
|
})
|
|
}
|
|
|
|
// GetGDPR returnerar GDPR-register
|
|
func (h *ComplianceHandler) GetGDPR(w http.ResponseWriter, r *http.Request) {
|
|
records := []GDPRRecord{
|
|
{
|
|
ID: "gdpr-001",
|
|
EntityID: "lvx-ab",
|
|
Purpose: "quiXzoom användarregistrering och verifiering",
|
|
DataSubjects: []string{"Zoomers", "Kunder"},
|
|
DataTypes: []string{"namn", "email", "telefon", "ID-dokument", "selfie"},
|
|
LegalBasis: "contract",
|
|
Retention: "3 år efter avslutat avtal",
|
|
Processors: []string{"AWS eu-north-1", "Stripe"},
|
|
DPAExists: true,
|
|
CrossBorder: false,
|
|
ImpactAssessment: true,
|
|
LastReview: "2026-05-15",
|
|
},
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"ok": true,
|
|
"records": records,
|
|
})
|
|
}
|
|
|
|
// GetRisks returnerar riskregister
|
|
func (h *ComplianceHandler) GetRisks(w http.ResponseWriter, r *http.Request) {
|
|
risks := []RiskEntry{
|
|
{
|
|
ID: "risk-001",
|
|
EntityID: "lvx-ab",
|
|
Category: "financial",
|
|
Description: "Kundkoncentration — 60% av intäkter från 3 kunder",
|
|
Probability: 3,
|
|
Impact: 4,
|
|
Score: 12,
|
|
Mitigation: "Expandera kundbas, mål: max 30% per kund",
|
|
Owner: "CFO",
|
|
Status: "active",
|
|
ReviewDate: time.Now().Add(30 * 24 * time.Hour),
|
|
},
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"ok": true,
|
|
"risks": risks,
|
|
"summary": map[string]interface{}{
|
|
"total": len(risks),
|
|
"high_risk": 1,
|
|
"medium_risk": 2,
|
|
"low_risk": 1,
|
|
"mitigated": 1,
|
|
"exposure_sek": 500000,
|
|
},
|
|
})
|
|
}
|
|
|
|
// GetLegalCases returnerar juridiska ärenden från databasen
|
|
func (h *ComplianceHandler) GetLegalCases(w http.ResponseWriter, r *http.Request) {
|
|
rows, err := h.DB.Query(`
|
|
SELECT case_id, entity_id, title, case_type, status, priority, description, opposing_party, lawyer, opened_at, value, currency
|
|
FROM boc_legal_cases
|
|
ORDER BY opened_at DESC
|
|
`)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "database error")
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
var cases []LegalCase
|
|
for rows.Next() {
|
|
var c LegalCase
|
|
var lawyer sql.NullString
|
|
if err := rows.Scan(&c.ID, &c.EntityID, &c.Title, &c.Type, &c.Status, &c.Priority, &c.Description, &c.OpposingParty, &lawyer, &c.OpenedAt, &c.Value, &c.Currency); err != nil {
|
|
continue
|
|
}
|
|
if lawyer.Valid {
|
|
c.Lawyer = lawyer.String
|
|
}
|
|
cases = append(cases, c)
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"ok": true,
|
|
"cases": cases,
|
|
"summary": map[string]interface{}{
|
|
"total": len(cases),
|
|
"active": countCasesByStatus(cases, "active"),
|
|
"pending": countCasesByStatus(cases, "pending"),
|
|
"closed": countCasesByStatus(cases, "closed"),
|
|
"exposure": calculateExposure(cases),
|
|
},
|
|
})
|
|
}
|
|
|
|
// GetPolicies returnerar policies
|
|
func (h *ComplianceHandler) GetPolicies(w http.ResponseWriter, r *http.Request) {
|
|
policies := []Policy{
|
|
{
|
|
ID: "pol-001",
|
|
Title: "Information Security Policy",
|
|
Category: "security",
|
|
Version: "2.1",
|
|
Status: "active",
|
|
ApprovedBy: "Erik Svensson",
|
|
ApprovedAt: time.Now().Add(-90 * 24 * time.Hour),
|
|
ReviewDate: time.Now().Add(275 * 24 * time.Hour),
|
|
EntityID: "lvx-ab",
|
|
URL: "/policies/infosec-v2.1.pdf",
|
|
},
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"ok": true,
|
|
"policies": policies,
|
|
})
|
|
}
|
|
|
|
func countCasesByStatus(cases []LegalCase, status string) int {
|
|
count := 0
|
|
for _, c := range cases {
|
|
if c.Status == status {
|
|
count++
|
|
}
|
|
}
|
|
return count
|
|
}
|
|
|
|
func calculateExposure(cases []LegalCase) float64 {
|
|
var total float64
|
|
for _, c := range cases {
|
|
if c.Status == "active" || c.Status == "pending" {
|
|
total += c.Value
|
|
}
|
|
}
|
|
return total
|
|
}
|