package server import ( "encoding/json" "fmt" "io" "net/http" "strings" "time" "github.com/openp2p-cn/inp2p/internal/store" ) // helpers func BearerToken(r *http.Request) string { h := r.Header.Get("Authorization") if h == "" { return "" } parts := strings.SplitN(h, " ", 2) if len(parts) != 2 { return "" } if strings.ToLower(parts[0]) != "bearer" { return "" } return strings.TrimSpace(parts[1]) } func writeJSON(w http.ResponseWriter, status int, body string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) io.WriteString(w, body) } func (s *Server) HandleAdminCreateTenant(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { tenants, err := s.store.ListTenants() if err != nil { writeJSON(w, http.StatusInternalServerError, `{"error":1,"message":"list tenants failed"}`) return } resp := struct { Error int `json:"error"` Message string `json:"message"` Tenants []store.Tenant `json:"tenants"` }{0, "ok", tenants} b, _ := json.Marshal(resp) writeJSON(w, http.StatusOK, string(b)) return } // update tenant status via /api/v1/admin/tenants/{id}?status=0|1 if r.Method == http.MethodPost && strings.Contains(r.URL.Path, "/admin/tenants/") { parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/") if len(parts) >= 4 { var id int64 _, _ = fmt.Sscanf(parts[len(parts)-1], "%d", &id) st := r.URL.Query().Get("status") if id > 0 && st != "" { status := 0 if st == "1" { status = 1 } _ = s.store.UpdateTenantStatus(id, status) if ac := GetAccessContext(r); ac != nil { _ = s.store.AddAuditLog(ac.Kind, fmt.Sprintf("%d", ac.UserID), "tenant_status", "tenant", fmt.Sprintf("%d", id), fmt.Sprintf("status=%d", status), r.RemoteAddr) } writeJSON(w, http.StatusOK, `{"error":0,"message":"ok"}`) return } } } if r.Method != http.MethodPost { writeJSON(w, http.StatusMethodNotAllowed, `{"error":1,"message":"method not allowed"}`) return } var req struct { Name string `json:"name"` AdminPassword string `json:"admin_password"` OperatorPassword string `json:"operator_password"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Name == "" { writeJSON(w, http.StatusBadRequest, `{"error":1,"message":"bad request"}`) return } if s.store == nil { writeJSON(w, http.StatusInternalServerError, `{"error":1,"message":"store not ready"}`) return } var ten *store.Tenant var admin *store.User var op *store.User var err error if req.AdminPassword != "" && req.OperatorPassword != "" { ten, admin, op, err = s.store.CreateTenantWithUsers(req.Name, req.AdminPassword, req.OperatorPassword) } else { ten, err = s.store.CreateTenant(req.Name) } if err != nil { writeJSON(w, http.StatusInternalServerError, `{"error":1,"message":"create tenant failed"}`) return } resp := struct { Error int `json:"error"` Message string `json:"message"` Tenant int64 `json:"tenant_id"` Subnet string `json:"subnet"` AdminUser string `json:"admin_user"` OperatorUser string `json:"operator_user"` }{0, "ok", ten.ID, ten.Subnet, "", ""} if admin != nil { resp.AdminUser = admin.Email } if op != nil { resp.OperatorUser = op.Email } b, _ := json.Marshal(resp) writeJSON(w, http.StatusOK, string(b)) } func (s *Server) HandleAdminCreateAPIKey(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost && r.Method != http.MethodGet { writeJSON(w, http.StatusMethodNotAllowed, `{"error":1,"message":"method not allowed"}`) return } if s.store == nil { writeJSON(w, http.StatusInternalServerError, `{"error":1,"message":"store not ready"}`) return } // /api/v1/admin/tenants/{id}/keys parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/") if len(parts) < 6 || parts[5] != "keys" { writeJSON(w, http.StatusBadRequest, `{"error":1,"message":"bad request"}`) return } // parts: api v1 admin tenants {id} keys idPart := parts[4] var tenantID int64 _, _ = fmt.Sscanf(idPart, "%d", &tenantID) if tenantID == 0 { writeJSON(w, http.StatusBadRequest, `{"error":1,"message":"bad request"}`) return } if r.Method == http.MethodGet { keys, err := s.store.ListAPIKeys(tenantID) if err != nil { writeJSON(w, http.StatusInternalServerError, `{"error":1,"message":"list keys failed"}`) return } resp := struct { Error int `json:"error"` Message string `json:"message"` Keys []store.APIKey `json:"keys"` }{0, "ok", keys} b, _ := json.Marshal(resp) writeJSON(w, http.StatusOK, string(b)) return } // update key status via /api/v1/admin/tenants/{id}/keys/{keyId}?status=0|1 if strings.Contains(r.URL.Path, "/keys/") { parts2 := strings.Split(strings.Trim(r.URL.Path, "/"), "/") var keyID int64 _, _ = fmt.Sscanf(parts2[len(parts2)-1], "%d", &keyID) st := r.URL.Query().Get("status") if keyID > 0 && st != "" { status := 0 if st == "1" { status = 1 } _ = s.store.UpdateAPIKeyStatus(keyID, status) if ac := GetAccessContext(r); ac != nil { _ = s.store.AddAuditLog(ac.Kind, fmt.Sprintf("%d", ac.UserID), "apikey_status", "apikey", fmt.Sprintf("%d", keyID), fmt.Sprintf("status=%d", status), r.RemoteAddr) } writeJSON(w, http.StatusOK, `{"error":0,"message":"ok"}`) return } } var req struct { Scope string `json:"scope"` TTL int64 `json:"ttl"` // seconds } _ = json.NewDecoder(r.Body).Decode(&req) var ttl time.Duration if req.TTL > 0 { ttl = time.Duration(req.TTL) * time.Second } key, err := s.store.CreateAPIKey(tenantID, req.Scope, ttl) if err != nil { writeJSON(w, http.StatusInternalServerError, `{"error":1,"message":"create key failed"}`) return } resp := struct { Error int `json:"error"` Message string `json:"message"` APIKey string `json:"api_key"` Tenant int64 `json:"tenant_id"` }{0, "ok", key, tenantID} b, _ := json.Marshal(resp) writeJSON(w, http.StatusOK, string(b)) if ac := GetAccessContext(r); ac != nil { _ = s.store.AddAuditLog(ac.Kind, fmt.Sprintf("%d", ac.UserID), "apikey_create", "tenant", fmt.Sprintf("%d", tenantID), req.Scope, r.RemoteAddr) } } func (s *Server) HandleTenantEnroll(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost && r.Method != http.MethodGet { writeJSON(w, http.StatusMethodNotAllowed, `{"error":1,"message":"method not allowed"}`) return } // tenant auth by session/apikey if s.store == nil { writeJSON(w, http.StatusInternalServerError, `{"error":1,"message":"store not ready"}`) return } tok := BearerToken(r) ac, ok := s.ResolveTenantAccessToken(tok) if !ok || ac.TenantID <= 0 { writeJSON(w, http.StatusUnauthorized, `{"error":1,"message":"unauthorized"}`) return } ten, err := s.store.GetTenantByID(ac.TenantID) if err != nil || ten == nil || ten.Status != 1 { writeJSON(w, http.StatusUnauthorized, `{"error":1,"message":"unauthorized"}`) return } if r.Method == http.MethodGet { tokens, err := s.store.ListEnrollTokens(ten.ID) if err != nil { writeJSON(w, http.StatusInternalServerError, `{"error":1,"message":"list enroll failed"}`) return } resp := struct { Error int `json:"error"` Message string `json:"message"` Enrolls []store.EnrollToken `json:"enrolls"` }{0, "ok", tokens} b, _ := json.Marshal(resp) writeJSON(w, http.StatusOK, string(b)) return } code, err := s.store.CreateEnrollToken(ten.ID, 10*time.Minute, 5) if err != nil { writeJSON(w, http.StatusInternalServerError, `{"error":1,"message":"create enroll failed"}`) return } resp := struct { Error int `json:"error"` Message string `json:"message"` Code string `json:"enroll_code"` Tenant int64 `json:"tenant_id"` }{0, "ok", code, ten.ID} b, _ := json.Marshal(resp) writeJSON(w, http.StatusOK, string(b)) } func (s *Server) HandleEnrollConsume(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeJSON(w, http.StatusMethodNotAllowed, `{"error":1,"message":"method not allowed"}`) return } // revoke support: /api/v1/enroll/consume/{id}?status=0 if strings.Contains(r.URL.Path, "/enroll/consume/") { parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/") if len(parts) >= 4 { var id int64 _, _ = fmt.Sscanf(parts[len(parts)-1], "%d", &id) st := r.URL.Query().Get("status") if id > 0 && st != "" { status := 0 if st == "1" { status = 1 } _ = s.store.UpdateEnrollStatus(id, status) writeJSON(w, http.StatusOK, `{"error":0,"message":"ok"}`) return } } } var req struct { Code string `json:"code"` NodeName string `json:"node"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Code == "" || req.NodeName == "" { writeJSON(w, http.StatusBadRequest, `{"error":1,"message":"bad request"}`) return } if s.store == nil { writeJSON(w, http.StatusInternalServerError, `{"error":1,"message":"store not ready"}`) return } et, err := s.store.ConsumeEnrollToken(req.Code) if err != nil { s.store.IncEnrollAttempt(req.Code) writeJSON(w, http.StatusUnauthorized, `{"error":1,"message":"invalid enroll"}`) return } cred, err := s.store.CreateNodeCredential(et.TenantID, req.NodeName) if err != nil { writeJSON(w, http.StatusInternalServerError, `{"error":1,"message":"create node failed"}`) return } resp := struct { Error int `json:"error"` Message string `json:"message"` NodeID int64 `json:"node_id"` NodeUUID string `json:"node_uuid"` NodeName string `json:"node_name"` Alias string `json:"alias"` VirtualIP string `json:"virtual_ip"` Secret string `json:"node_secret"` Tenant int64 `json:"tenant_id"` CreatedAt int64 `json:"created_at"` }{0, "ok", cred.NodeID, cred.NodeUUID, cred.NodeName, cred.Alias, cred.VirtualIP, cred.Secret, cred.TenantID, cred.CreatedAt} b, _ := json.Marshal(resp) writeJSON(w, http.StatusOK, string(b)) } // placeholder to avoid unused import var _ = store.Tenant{}