Files
NexaVPN/backend/internal/device/service.go
nessi a52777602f feat: resolve policy destinations before device enrollment with fallback default
Fetch policy destinations before calling repo.Enroll instead of after, passing destinations to enrollment creation. Add fallback to default 172.16.10.0/24 destination when no policies are resolved. Re-resolve destinations after enrollment with device ID for final response.
2026-03-17 19:59:40 +01:00

163 lines
4.5 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.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
}