inou/api/api_access.go

169 lines
4.2 KiB
Go

package main
import (
"encoding/json"
"net/http"
"strings"
"inou/lib"
)
type AccessRecord struct {
Accessor string `json:"accessor"`
Target string `json:"target"`
Name string `json:"name,omitempty"`
Relation int `json:"relation"`
IsCareReceiver bool `json:"is_care_receiver"`
CanEdit bool `json:"can_edit"`
CreatedAt int64 `json:"created_at,omitempty"`
AccessedAt int64 `json:"accessed_at,omitempty"`
}
type AccessRequest struct {
Accessor string `json:"accessor"`
Target string `json:"target"`
Relation int `json:"relation"`
IsCareReceiver bool `json:"is_care_receiver"`
CanEdit bool `json:"can_edit"`
Delete bool `json:"delete"`
Touch bool `json:"touch"`
}
func handleAccess(w http.ResponseWriter, r *http.Request) {
if !isLocalhost(r) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
if r.Method == "GET" {
handleAccessGet(w, r)
return
}
if r.Method == "POST" {
handleAccessPost(w, r)
return
}
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
func handleAccessGet(w http.ResponseWriter, r *http.Request) {
accessorHex := r.URL.Query().Get("accessor")
targetHex := r.URL.Query().Get("target")
w.Header().Set("Content-Type", "application/json")
// Single record lookup
if accessorHex != "" && targetHex != "" {
access, err := lib.AccessGet(accessorHex, targetHex)
if err != nil || access == nil {
json.NewEncoder(w).Encode(map[string]interface{}{"found": false})
return
}
json.NewEncoder(w).Encode(AccessRecord{
Accessor: accessorHex,
Target: targetHex,
Relation: 0, // Relation removed from RBAC
IsCareReceiver: false, // deprecated field
CanEdit: (access.Ops & lib.PermWrite) != 0,
CreatedAt: access.CreatedAt,
AccessedAt: access.CreatedAt, // Use CreatedAt as fallback
})
return
}
// List all with access to target (includes names from dossiers join)
if targetHex != "" {
records, err := lib.AccessListByTargetWithNames(targetHex)
if err != nil {
json.NewEncoder(w).Encode([]interface{}{})
return
}
var result []AccessRecord
for _, rec := range records {
result = append(result, AccessRecord{
Accessor: rec["accessor_id"].(string),
Target: targetHex,
Name: rec["name"].(string),
Relation: rec["relation"].(int),
IsCareReceiver: rec["is_care_receiver"].(bool),
CanEdit: rec["can_edit"].(bool),
})
}
json.NewEncoder(w).Encode(result)
return
}
http.Error(w, "target required", http.StatusBadRequest)
}
func handleAccessPost(w http.ResponseWriter, r *http.Request) {
var req AccessRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
if req.Accessor == "" || req.Target == "" {
http.Error(w, "accessor and target required", http.StatusBadRequest)
return
}
accessorID := req.Accessor
targetID := req.Target
w.Header().Set("Content-Type", "application/json")
if req.Delete {
err := lib.AccessRemove(accessorID, targetID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(map[string]interface{}{"deleted": true})
return
}
if req.Touch {
err := lib.AccessUpdateTimestamp(accessorID, targetID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(map[string]interface{}{"touched": true})
return
}
// Grant access using new RBAC system
ops := lib.PermRead
if req.CanEdit {
ops |= lib.PermWrite
}
if err := lib.GrantAccess(targetID, accessorID, targetID, ops, req.Relation); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(AccessRecord{
Accessor: req.Accessor,
Target: req.Target,
Relation: req.Relation,
IsCareReceiver: req.IsCareReceiver,
CanEdit: req.CanEdit,
})
}
func isLocalhost(r *http.Request) bool {
ip := r.RemoteAddr
return strings.HasPrefix(ip, "127.0.0.1") || strings.HasPrefix(ip, "[::1]")
}
func boolToInt(b bool) int {
if b {
return 1
}
return 0
}