security: Add proper authentication, RBAC, and tenant isolation
- 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
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
// LandvexHandler hanterar Landvex bolagskontroll
|
||||
type LandvexHandler struct {
|
||||
DB *sql.DB
|
||||
}
|
||||
|
||||
// NewLandvexHandler skapar en ny handler
|
||||
func NewLandvexHandler(db *sql.DB) *LandvexHandler {
|
||||
return &LandvexHandler{DB: db}
|
||||
}
|
||||
|
||||
// Entity representerar en juridisk enhet
|
||||
type Entity struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
OrgNumber string `json:"org_number"`
|
||||
Country string `json:"country"`
|
||||
City string `json:"city"`
|
||||
Address string `json:"address"`
|
||||
Status string `json:"status"`
|
||||
FoundedAt time.Time `json:"founded_at"`
|
||||
ParentID *string `json:"parent_id,omitempty"`
|
||||
Ownership float64 `json:"ownership_percent"`
|
||||
CEO string `json:"ceo"`
|
||||
BoardMembers []Person `json:"board_members"`
|
||||
Employees int `json:"employees"`
|
||||
Revenue float64 `json:"revenue"`
|
||||
Currency string `json:"currency"`
|
||||
TaxStatus string `json:"tax_status"`
|
||||
ComplianceStatus string `json:"compliance_status"`
|
||||
}
|
||||
|
||||
// Person representerar en person
|
||||
type Person struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Role string `json:"role"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Nationality string `json:"nationality"`
|
||||
Since string `json:"since"`
|
||||
}
|
||||
|
||||
// Document representerar ett dokument
|
||||
type Document struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Type string `json:"type"`
|
||||
EntityID string `json:"entity_id"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
ExpiresAt *time.Time `json:"expires_at,omitempty"`
|
||||
SignedBy []string `json:"signed_by"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// ComplianceItem representerar ett compliance-krav
|
||||
type ComplianceItem struct {
|
||||
ID string `json:"id"`
|
||||
EntityID string `json:"entity_id"`
|
||||
Title string `json:"title"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
DueDate time.Time `json:"due_date"`
|
||||
CompletedAt *time.Time `json:"completed_at,omitempty"`
|
||||
Responsible string `json:"responsible"`
|
||||
Priority string `json:"priority"`
|
||||
}
|
||||
|
||||
// GetEntities returnerar alla Landvex-enheter från databasen
|
||||
func (h *LandvexHandler) GetEntities(w http.ResponseWriter, r *http.Request) {
|
||||
rows, err := h.DB.Query(`
|
||||
SELECT entity_id, name, jurisdiction, entity_type, status
|
||||
FROM boc_landvex_entities
|
||||
WHERE status = 'active'
|
||||
ORDER BY name
|
||||
`)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "database error")
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var entities []Entity
|
||||
for rows.Next() {
|
||||
var e Entity
|
||||
if err := rows.Scan(&e.ID, &e.Name, &e.Country, &e.Type, &e.Status); err != nil {
|
||||
continue
|
||||
}
|
||||
entities = append(entities, e)
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"ok": true,
|
||||
"entities": entities,
|
||||
})
|
||||
}
|
||||
|
||||
// GetEntity returnerar en specifik enhet
|
||||
func (h *LandvexHandler) GetEntity(w http.ResponseWriter, r *http.Request) {
|
||||
entityID := chi.URLParam(r, "id")
|
||||
|
||||
var e Entity
|
||||
err := h.DB.QueryRow(`
|
||||
SELECT entity_id, name, jurisdiction, entity_type, status
|
||||
FROM boc_landvex_entities
|
||||
WHERE entity_id = $1
|
||||
`, entityID).Scan(&e.ID, &e.Name, &e.Country, &e.Type, &e.Status)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusNotFound, "entity not found")
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"ok": true,
|
||||
"entity": e,
|
||||
})
|
||||
}
|
||||
|
||||
// GetDocuments returnerar alla dokument
|
||||
func (h *LandvexHandler) GetDocuments(w http.ResponseWriter, r *http.Request) {
|
||||
documents := []Document{
|
||||
{
|
||||
ID: "doc-001",
|
||||
Title: "Styrelseprotokoll 2026-01-15",
|
||||
Type: "board_minutes",
|
||||
EntityID: "lvx-ab",
|
||||
Status: "signed",
|
||||
CreatedAt: time.Now().Add(-180 * 24 * time.Hour),
|
||||
SignedBy: []string{"Erik Svensson", "Johan Berglund"},
|
||||
URL: "/docs/board-2026-01-15.pdf",
|
||||
},
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"ok": true,
|
||||
"documents": documents,
|
||||
})
|
||||
}
|
||||
|
||||
// GetCompliance returnerar compliance-krav från databasen
|
||||
func (h *LandvexHandler) GetCompliance(w http.ResponseWriter, r *http.Request) {
|
||||
rows, err := h.DB.Query(`
|
||||
SELECT id, entity_id, category, title, status, due_date, completed_at, notes
|
||||
FROM boc_landvex_compliance
|
||||
ORDER BY due_date ASC
|
||||
`)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "database error")
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var items []ComplianceItem
|
||||
for rows.Next() {
|
||||
var c ComplianceItem
|
||||
var notes sql.NullString
|
||||
if err := rows.Scan(&c.ID, &c.EntityID, &c.Type, &c.Title, &c.Status, &c.DueDate, &c.CompletedAt, ¬es); err != nil {
|
||||
continue
|
||||
}
|
||||
items = append(items, c)
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"ok": true,
|
||||
"compliance_items": items,
|
||||
"summary": map[string]interface{}{
|
||||
"total": len(items),
|
||||
"pending": countByStatus(items, "pending"),
|
||||
"overdue": countByStatus(items, "overdue"),
|
||||
"completed": countByStatus(items, "completed"),
|
||||
"this_quarter": len(items),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// GetOwnership returnerar ägarstruktur från databasen
|
||||
func (h *LandvexHandler) GetOwnership(w http.ResponseWriter, r *http.Request) {
|
||||
rows, err := h.DB.Query(`
|
||||
SELECT e.entity_id, e.name, e.jurisdiction, e.entity_type,
|
||||
o.owner_name, o.ownership_percent, o.parent_entity_id
|
||||
FROM boc_landvex_entities e
|
||||
LEFT JOIN boc_landvex_ownership o ON e.entity_id = o.entity_id
|
||||
WHERE e.status = 'active'
|
||||
ORDER BY e.name
|
||||
`)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "database error")
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var entities []map[string]interface{}
|
||||
for rows.Next() {
|
||||
var entityID, name, jurisdiction, entityType, ownerName string
|
||||
var ownership float64
|
||||
var parentID sql.NullString
|
||||
if err := rows.Scan(&entityID, &name, &jurisdiction, &entityType, &ownerName, &ownership, &parentID); err != nil {
|
||||
continue
|
||||
}
|
||||
entities = append(entities, map[string]interface{}{
|
||||
"id": entityID,
|
||||
"name": name,
|
||||
"jurisdiction": jurisdiction,
|
||||
"type": entityType,
|
||||
"owner": ownerName,
|
||||
"ownership": ownership,
|
||||
})
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"ok": true,
|
||||
"ownership": map[string]interface{}{
|
||||
"structure": "linear",
|
||||
"ultimate_beneficial_owner": map[string]interface{}{
|
||||
"name": "Erik Svensson",
|
||||
"nationality": "SE",
|
||||
"ownership": 100,
|
||||
},
|
||||
"entities": entities,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func countByStatus(items []ComplianceItem, status string) int {
|
||||
count := 0
|
||||
for _, item := range items {
|
||||
if item.Status == status {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func strPtr(s string) *string {
|
||||
return &s
|
||||
}
|
||||
Reference in New Issue
Block a user