package handler import ( "fmt" "net/http" "github.com/labstack/echo/v4" "github.com/labstack/gommon/log" "github.com/rs/xid" "github.com/ngoduykhanh/wireguard-ui/opnsense" "github.com/ngoduykhanh/wireguard-ui/store" ) // maxOPNsenseUploadSize caps the accepted config.xml upload size. const maxOPNsenseUploadSize = 5 << 20 // 5 MiB // PreviewOPNsenseImport handler accepts an uploaded OPNsense config.xml, // parses and maps its WireGuard server/client definitions, and returns the // staged (admin-editable) preview as JSON. It never touches the store - // nothing is written, nothing is applied. Admin-only. func PreviewOPNsenseImport(db store.IStore) echo.HandlerFunc { return func(c echo.Context) error { fileHeader, err := c.FormFile("config") if err != nil { return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please upload a config.xml file"}) } if fileHeader.Size > maxOPNsenseUploadSize { return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "config.xml is too large (max 5 MiB)"}) } src, err := fileHeader.Open() if err != nil { log.Error("Cannot open uploaded OPNsense config.xml: ", err) return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Cannot read uploaded file"}) } defer src.Close() parsed, err := opnsense.Parse(src) if err != nil { log.Error("Cannot parse OPNsense config.xml: ", err) return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, fmt.Sprintf("Cannot parse config.xml: %v", err)}) } existingServers, err := db.GetServers() if err != nil { log.Error("Cannot list existing servers: ", err) return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot list existing servers"}) } existingIDs := make([]string, 0, len(existingServers)) for _, s := range existingServers { existingIDs = append(existingIDs, s.ID) } preview := opnsense.ToPreview(parsed, existingIDs) return c.JSON(http.StatusOK, preview) } } // CommitOPNsenseImport handler accepts the (possibly admin-edited) preview // JSON produced by PreviewOPNsenseImport - not the original XML - and // writes the corresponding model.Server/ServerSetting/Client records via // the store. Servers/clients that fail validation, or clients whose public // key collides with one already in the store, are skipped and reported; // the rest of the batch still commits. This never applies the resulting // WireGuard config (no wg-quick/systemctl calls) - that remains a separate, // manual "Apply" step elsewhere in the app. Admin-only. func CommitOPNsenseImport(db store.IStore) echo.HandlerFunc { return func(c echo.Context) error { var payload opnsense.PreviewResult if err := c.Bind(&payload); err != nil { return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Bad post data"}) } existingClients, err := db.GetClients(false) if err != nil { log.Error("Cannot list existing clients for duplicate check: ", err) return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot list existing clients"}) } knownPublicKeys := make(map[string]bool, len(existingClients)) for _, cd := range existingClients { knownPublicKeys[cd.Client.PublicKey] = true } type serverResult struct { ID string `json:"id"` Imported bool `json:"imported"` Error string `json:"error,omitempty"` ClientsCreated int `json:"clients_created"` ClientsSkipped []string `json:"clients_skipped,omitempty"` } results := make([]serverResult, 0, len(payload.Servers)) for _, ps := range payload.Servers { res := serverResult{ID: ps.ID} server, settings, clients, err := opnsense.ToModel(ps) if err != nil { res.Error = err.Error() results = append(results, res) continue } if _, err := db.GetServerByID(server.ID); err == nil { res.Error = "a server with this ID already exists" results = append(results, res) continue } if err := db.CreateServer(server); err != nil { res.Error = fmt.Sprintf("cannot create server: %v", err) results = append(results, res) continue } if err := db.SaveServerSettings(server.ID, settings); err != nil { log.Errorf("Server %s created but settings failed: %v", server.ID, err) } for _, client := range clients { if knownPublicKeys[client.PublicKey] { res.ClientsSkipped = append(res.ClientsSkipped, fmt.Sprintf("%s (duplicate public key)", client.Name)) continue } client.ID = xid.New().String() if err := db.SaveClient(client); err != nil { res.ClientsSkipped = append(res.ClientsSkipped, fmt.Sprintf("%s (%v)", client.Name, err)) continue } knownPublicKeys[client.PublicKey] = true res.ClientsCreated++ } res.Imported = true log.Infof("Imported server %s from OPNsense config (%d clients)", server.ID, res.ClientsCreated) results = append(results, res) } return c.JSON(http.StatusOK, results) } }