Add FindNextAvailableIP repository method to query ip_allocations table and find next available IP address within gateway VPN CIDR range. Query existing allocations from database and build used IP map. Iterate through CIDR range starting at offset to find first unused address. Update Enroll service method to call FindNextAvailableIP first with fallback to IPAM service Allocate method on error. Add netip and errors imports to repository
166 lines
4.7 KiB
Go
166 lines
4.7 KiB
Go
package device
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"nexavpn/backend/internal/gateway"
|
|
"nexavpn/backend/internal/ipam"
|
|
"nexavpn/backend/internal/policy"
|
|
"nexavpn/backend/internal/profile"
|
|
)
|
|
|
|
type Service struct {
|
|
repo Repository
|
|
policyService *policy.Service
|
|
gatewayService *gateway.Service
|
|
ipamService *ipam.Service
|
|
}
|
|
|
|
func NewService(repo Repository, policyService *policy.Service, gatewayService *gateway.Service, ipamService *ipam.Service) *Service {
|
|
return &Service{
|
|
repo: repo,
|
|
policyService: policyService,
|
|
gatewayService: gatewayService,
|
|
ipamService: ipamService,
|
|
}
|
|
}
|
|
|
|
func (s *Service) Enroll(ctx context.Context, userID uuid.UUID, input EnrollRequest, privateKeyPlaceholder string) (EnrollmentResponse, error) {
|
|
selectedGateway, err := s.gatewayService.SelectActive(ctx)
|
|
if err != nil {
|
|
return EnrollmentResponse{}, err
|
|
}
|
|
|
|
assignedIP, err := s.repo.FindNextAvailableIP(ctx, selectedGateway.ID, selectedGateway.VPNCIDR, 10)
|
|
if err != nil {
|
|
assignedIP, err = s.ipamService.Allocate(selectedGateway.VPNCIDR, 10)
|
|
if err != nil {
|
|
return EnrollmentResponse{}, err
|
|
}
|
|
}
|
|
|
|
destinations, err := s.policyService.ResolveDestinations(ctx, userID, nil)
|
|
if err != nil {
|
|
return EnrollmentResponse{}, err
|
|
}
|
|
if len(destinations) == 0 {
|
|
destinations = []string{"172.16.10.0/24"}
|
|
}
|
|
|
|
enrollment, err := s.repo.Enroll(ctx, userID, selectedGateway.ID, input, assignedIP, selectedGateway.DNSServers, destinations)
|
|
if err != nil {
|
|
return EnrollmentResponse{}, err
|
|
}
|
|
|
|
destinations, err = s.policyService.ResolveDestinations(ctx, userID, &enrollment.Device.ID)
|
|
if err != nil {
|
|
return EnrollmentResponse{}, err
|
|
}
|
|
if len(destinations) == 0 {
|
|
destinations = []string{"172.16.10.0/24"}
|
|
}
|
|
|
|
enrollment.Peer = PeerView{
|
|
AssignedIP: assignedIP,
|
|
DNSServers: selectedGateway.DNSServers,
|
|
AllowedIPs: destinations,
|
|
Gateway: GatewayView{
|
|
ID: selectedGateway.ID,
|
|
Name: selectedGateway.Name,
|
|
Endpoint: selectedGateway.Endpoint,
|
|
PublicKey: selectedGateway.PublicKey,
|
|
},
|
|
ProfileRevision: 1,
|
|
}
|
|
for _, destination := range destinations {
|
|
enrollment.Resources = append(enrollment.Resources, Resource{
|
|
Type: "cidr",
|
|
Value: destination,
|
|
Label: destination,
|
|
})
|
|
}
|
|
|
|
enrollment.Profile = ProfileView{
|
|
Format: "wireguard",
|
|
Content: profile.BuildWireGuardConfig(profile.BuildInput{
|
|
PrivateKey: privateKeyPlaceholder,
|
|
Address: assignedIP,
|
|
DNSServers: selectedGateway.DNSServers,
|
|
ServerPublicKey: selectedGateway.PublicKey,
|
|
ServerEndpoint: selectedGateway.Endpoint,
|
|
AllowedIPs: destinations,
|
|
PersistentKeepal: 25,
|
|
}),
|
|
}
|
|
|
|
return enrollment, nil
|
|
}
|
|
|
|
func (s *Service) ListByUser(ctx context.Context, userID uuid.UUID) ([]Device, error) {
|
|
return s.repo.ListByUser(ctx, userID)
|
|
}
|
|
|
|
func (s *Service) ListAll(ctx context.Context) ([]Device, error) {
|
|
return s.repo.ListAll(ctx)
|
|
}
|
|
|
|
func (s *Service) GetLatestEnrollmentByUser(ctx context.Context, userID uuid.UUID) (EnrollmentResponse, error) {
|
|
enrollment, err := s.repo.GetLatestEnrollmentByUser(ctx, userID)
|
|
if err != nil {
|
|
return EnrollmentResponse{}, err
|
|
}
|
|
return withDebugProfile(enrollment), nil
|
|
}
|
|
|
|
func (s *Service) GetEnrollmentByDeviceID(ctx context.Context, deviceID uuid.UUID) (EnrollmentResponse, error) {
|
|
enrollment, err := s.repo.GetEnrollmentByDeviceID(ctx, deviceID)
|
|
if err != nil {
|
|
return EnrollmentResponse{}, err
|
|
}
|
|
return withDebugProfile(enrollment), nil
|
|
}
|
|
|
|
func (s *Service) GetConnectionStatus(ctx context.Context, userID uuid.UUID) (ConnectionStatus, error) {
|
|
enrollment, err := s.repo.GetLatestEnrollmentByUser(ctx, userID)
|
|
if err != nil {
|
|
return ConnectionStatus{
|
|
Status: "disconnected",
|
|
Resources: []Resource{},
|
|
}, nil
|
|
}
|
|
|
|
lastSync := "just now"
|
|
return ConnectionStatus{
|
|
Status: "provisioned",
|
|
AssignedIP: enrollment.Peer.AssignedIP,
|
|
LastSyncTime: &lastSync,
|
|
Resources: enrollment.Resources,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Service) Revoke(ctx context.Context, deviceID uuid.UUID) error {
|
|
return s.repo.Revoke(ctx, deviceID)
|
|
}
|
|
|
|
func (s *Service) Rotate(ctx context.Context, deviceID uuid.UUID) error {
|
|
return s.repo.Rotate(ctx, deviceID)
|
|
}
|
|
|
|
func withDebugProfile(enrollment EnrollmentResponse) EnrollmentResponse {
|
|
enrollment.Profile = ProfileView{
|
|
Format: "wireguard",
|
|
Content: profile.BuildWireGuardConfig(profile.BuildInput{
|
|
PrivateKey: "__CLIENT_PRIVATE_KEY_REQUIRED__",
|
|
Address: enrollment.Peer.AssignedIP,
|
|
DNSServers: enrollment.Peer.DNSServers,
|
|
ServerPublicKey: enrollment.Peer.Gateway.PublicKey,
|
|
ServerEndpoint: enrollment.Peer.Gateway.Endpoint,
|
|
AllowedIPs: enrollment.Peer.AllowedIPs,
|
|
PersistentKeepal: 25,
|
|
}),
|
|
}
|
|
return enrollment
|
|
}
|