| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799 |
- package logic
- import (
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "sort"
- "time"
- "github.com/google/uuid"
- "github.com/gravitl/netmaker/database"
- "github.com/gravitl/netmaker/db"
- "github.com/gravitl/netmaker/logger"
- "github.com/gravitl/netmaker/models"
- "github.com/gravitl/netmaker/mq"
- "github.com/gravitl/netmaker/schema"
- "golang.org/x/exp/slog"
- "github.com/gravitl/netmaker/logic"
- )
- // JITStatusResponse - response for JIT status check
- type JITStatusResponse struct {
- HasAccess bool `json:"has_access"`
- Grant *schema.JITGrant `json:"grant,omitempty"`
- Request *schema.JITRequest `json:"request,omitempty"`
- PendingRequest bool `json:"pending_request"`
- }
- // EnableJITOnNetwork - enables JIT on a network and disconnects existing ext clients
- func EnableJITOnNetwork(networkID string) error {
- // Check if JIT feature is enabled
- featureFlags := GetFeatureFlags()
- if !featureFlags.EnableJIT {
- return errors.New("JIT feature is not enabled")
- }
- network, err := logic.GetNetwork(networkID)
- if err != nil {
- return fmt.Errorf("failed to get network: %w", err)
- }
- network.JITEnabled = "yes"
- network.SetNetworkLastModified()
- if err := logic.SaveNetwork(&network); err != nil {
- return fmt.Errorf("failed to save network: %w", err)
- }
- // Disconnect all ext clients from this network
- if err := DisconnectExtClientsFromNetwork(networkID); err != nil {
- logger.Log(0, "failed to disconnect ext clients when enabling JIT:", err.Error())
- // Don't fail the operation, just log
- }
- return nil
- }
- // DisableJITOnNetwork - disables JIT on a network
- func DisableJITOnNetwork(networkID string) error {
- network, err := logic.GetNetwork(networkID)
- if err != nil {
- return fmt.Errorf("failed to get network: %w", err)
- }
- network.JITEnabled = "no"
- network.SetNetworkLastModified()
- return logic.SaveNetwork(&network)
- }
- // CreateJITRequest - creates a new JIT access request
- func CreateJITRequest(networkID, userName, reason string) (*schema.JITRequest, error) {
- // Check if JIT feature is enabled
- featureFlags := GetFeatureFlags()
- if !featureFlags.EnableJIT {
- return nil, errors.New("JIT feature is not enabled")
- }
- ctx := db.WithContext(context.Background())
- // Check if network exists and has JIT enabled
- network, err := logic.GetNetwork(networkID)
- if err != nil {
- return nil, fmt.Errorf("network not found: %w", err)
- }
- if network.JITEnabled != "yes" {
- return nil, errors.New("JIT is not enabled on this network")
- }
- // Check if user already has an active grant
- hasAccess, _, err := CheckJITAccess(networkID, userName)
- if err == nil && hasAccess {
- return nil, errors.New("user already has active access grant")
- }
- // Check if there's already a pending request
- request := schema.JITRequest{
- NetworkID: networkID,
- UserID: userName,
- }
- pendingRequests, err := request.ListPendingByNetwork(ctx)
- if err == nil {
- for _, req := range pendingRequests {
- if req.UserID == userName {
- return nil, errors.New("user already has a pending request")
- }
- }
- }
- // Create new request
- newRequest := schema.JITRequest{
- ID: uuid.New().String(),
- NetworkID: networkID,
- UserID: userName,
- UserName: userName,
- Reason: reason,
- Status: "pending",
- RequestedAt: time.Now().UTC(),
- }
- if err := newRequest.Create(ctx); err != nil {
- return nil, fmt.Errorf("failed to create request: %w", err)
- }
- return &newRequest, nil
- }
- // ApproveJITRequest - approves a JIT request and creates a grant
- func ApproveJITRequest(requestID string, expiresAt time.Time, approvedBy string) (*schema.JITGrant, *schema.JITRequest, error) {
- ctx := db.WithContext(context.Background())
- // Get the request
- request := schema.JITRequest{ID: requestID}
- if err := request.Get(ctx); err != nil {
- return nil, nil, fmt.Errorf("request not found: %w", err)
- }
- if request.Status != "pending" {
- return nil, nil, errors.New("request is not pending")
- }
- // Update request status
- now := time.Now().UTC()
- // Ensure expiresAt is in UTC
- expiresAt = expiresAt.UTC()
- // Calculate duration in hours for storage
- durationHours := int(expiresAt.Sub(now).Hours())
- if durationHours < 1 {
- durationHours = 1 // Minimum 1 hour
- }
- request.Status = "approved"
- request.ApprovedAt = now
- request.ApprovedBy = approvedBy
- request.DurationHours = durationHours
- request.ExpiresAt = expiresAt
- if err := request.Update(ctx); err != nil {
- return nil, nil, fmt.Errorf("failed to update request: %w", err)
- }
- // Delete any existing grants for this user on this network
- if err := deactivateUserGrants(ctx, request.NetworkID, request.UserID); err != nil {
- slog.Warn("failed to delete existing grants", "error", err)
- }
- // Create new grant
- grant := schema.JITGrant{
- ID: uuid.New().String(),
- NetworkID: request.NetworkID,
- UserID: request.UserID,
- RequestID: request.ID,
- GrantedAt: now,
- ExpiresAt: expiresAt,
- }
- if err := grant.Create(ctx); err != nil {
- return nil, nil, fmt.Errorf("failed to create grant: %w", err)
- }
- return &grant, &request, nil
- }
- // DenyJITRequest - denies a JIT request and returns the updated request
- func DenyJITRequest(requestID string, deniedBy string) (*schema.JITRequest, error) {
- ctx := db.WithContext(context.Background())
- request := schema.JITRequest{ID: requestID}
- if err := request.Get(ctx); err != nil {
- return nil, fmt.Errorf("request not found: %w", err)
- }
- if request.Status != "pending" {
- return nil, errors.New("request is not pending")
- }
- now := time.Now().UTC()
- request.Status = "denied"
- request.ApprovedAt = now
- request.ApprovedBy = deniedBy
- if err := request.Update(ctx); err != nil {
- return nil, err
- }
- return &request, nil
- }
- // CheckJITAccess - checks if a user has active JIT access for a network
- func CheckJITAccess(networkID, userID string) (bool, *schema.JITGrant, error) {
- // Check if JIT feature is enabled
- featureFlags := GetFeatureFlags()
- if !featureFlags.EnableJIT {
- // Feature flag disabled, allow access (backward compatibility)
- return true, nil, nil
- }
- // Check if user is super admin, admin, network admin, or global network admin - skip JIT check for them
- user, err := logic.GetUser(userID)
- if err == nil {
- // Check platform role (super admin or admin)
- if user.PlatformRoleID == models.SuperAdminRole || user.PlatformRoleID == models.AdminRole {
- // Super admin or admin - bypass JIT check
- return true, nil, nil
- }
- if user.PlatformRoleID == models.PlatformUser {
- // Check network admin roles
- networkIDModel := models.NetworkID(networkID)
- allNetworksID := models.AllNetworks
- globalNetworksAdminRoleID := models.UserRoleID(fmt.Sprintf("global-%s", models.NetworkAdmin))
- // Check user groups for network admin roles
- for groupID := range user.UserGroups {
- groupData, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, groupID.String())
- if err != nil {
- continue
- }
- var group models.UserGroup
- if err := json.Unmarshal([]byte(groupData), &group); err != nil {
- continue
- }
- // Check if group has network admin role for this network
- if roles, ok := group.NetworkRoles[networkIDModel]; ok {
- for roleID := range roles {
- if roleID == models.NetworkAdmin {
- // User is in group with network admin role for this network - bypass JIT check
- return true, nil, nil
- }
- }
- }
- // Check if group has global network admin role
- if roles, ok := group.NetworkRoles[allNetworksID]; ok {
- for roleID := range roles {
- if roleID == models.NetworkAdmin || roleID == globalNetworksAdminRoleID {
- // User is in group with global network admin role - bypass JIT check
- return true, nil, nil
- }
- }
- }
- }
- }
- }
- ctx := db.WithContext(context.Background())
- // Check if network has JIT enabled
- network, err := logic.GetNetwork(networkID)
- if err != nil {
- return false, nil, fmt.Errorf("network not found: %w", err)
- }
- if network.JITEnabled != "yes" {
- // JIT not enabled, allow access
- return true, nil, nil
- }
- // Check for active grant
- grant := schema.JITGrant{
- NetworkID: networkID,
- UserID: userID,
- }
- activeGrant, err := grant.GetActiveByUserAndNetwork(ctx)
- if err != nil {
- // No active grant found
- return false, nil, nil
- }
- // Check if grant is expired
- if time.Now().UTC().After(activeGrant.ExpiresAt) {
- // Grant expired, delete it
- _ = activeGrant.Delete(ctx)
- return false, nil, nil
- }
- return true, activeGrant, nil
- }
- // JITRequestWithGrant - JIT request with grant ID for approved requests
- type JITRequestWithGrant struct {
- schema.JITRequest
- GrantID string `json:"grant_id,omitempty"` // Grant ID if request is approved
- }
- // GetNetworkJITRequests - gets JIT requests for a network, optionally filtered by status
- // statusFilter can be: "pending", "approved", "denied", "expired", or "" for all
- func GetNetworkJITRequests(networkID string, statusFilter string) ([]JITRequestWithGrant, error) {
- ctx := db.WithContext(context.Background())
- requests, _, err := GetNetworkJITRequestsPaginated(ctx, networkID, statusFilter, 1, 0)
- return requests, err
- }
- // GetNetworkJITRequestsPaginated - gets paginated JIT requests for a network, optionally filtered by status
- // statusFilter can be: "pending", "approved", "denied", "expired", or "" for all
- // page and pageSize control pagination. db.SetPagination will apply defaults (page=1, pageSize=10) if values are invalid.
- // Returns: requests, total count, error
- func GetNetworkJITRequestsPaginated(ctx context.Context, networkID string, statusFilter string, page, pageSize int) ([]JITRequestWithGrant, int64, error) {
- request := schema.JITRequest{NetworkID: networkID}
- var requests []schema.JITRequest
- var total int64
- var err error
- // Always set up pagination context - db.SetPagination handles defaults (page=1, pageSize=10)
- paginatedCtx := db.SetPagination(ctx, page, pageSize)
- // Get total count for pagination metadata
- if statusFilter == "" || statusFilter == "all" {
- total, err = request.CountByNetwork(ctx)
- if err != nil {
- return nil, 0, err
- }
- requests, err = request.ListByNetwork(paginatedCtx)
- if err != nil {
- return nil, 0, err
- }
- } else if statusFilter == "expired" {
- // Handle expired filter (approved requests that have expired)
- // For expired filter, we need to get all and filter in memory, then apply pagination
- allRequests, err := request.ListByNetwork(ctx)
- if err != nil {
- return nil, 0, err
- }
- now := time.Now().UTC()
- var filteredRequests []schema.JITRequest
- for _, req := range allRequests {
- // Include requests with status "expired" or "approved" requests that have passed expiration
- if req.Status == "expired" ||
- (req.Status == "approved" && !req.ExpiresAt.IsZero() && now.After(req.ExpiresAt)) {
- filteredRequests = append(filteredRequests, req)
- }
- }
- // Sort by requested_at DESC (most recent first)
- sort.Slice(filteredRequests, func(i, j int) bool {
- return filteredRequests[i].RequestedAt.After(filteredRequests[j].RequestedAt)
- })
- total = int64(len(filteredRequests))
- // Apply pagination manually for expired filter
- if pageSize > 0 {
- offset := (page - 1) * pageSize
- end := offset + pageSize
- if offset >= len(filteredRequests) {
- requests = []schema.JITRequest{}
- } else {
- if end > len(filteredRequests) {
- end = len(filteredRequests)
- }
- requests = filteredRequests[offset:end]
- }
- } else {
- requests = filteredRequests
- }
- } else {
- // Filter by status: pending, approved, or denied
- total, err = request.CountByStatusAndNetwork(ctx, statusFilter)
- if err != nil {
- return nil, 0, err
- }
- requests, err = request.ListByStatusAndNetwork(paginatedCtx, statusFilter)
- if err != nil {
- return nil, 0, err
- }
- }
- // Enrich requests with grant_id for approved requests
- result := make([]JITRequestWithGrant, 0, len(requests))
- for _, req := range requests {
- enriched := JITRequestWithGrant{
- JITRequest: req,
- }
- // If request is approved or expired, get the associated grant ID
- if req.Status == "approved" || req.Status == "expired" {
- grant := schema.JITGrant{RequestID: req.ID}
- if grantObj, err := grant.GetByRequestID(ctx); err == nil {
- enriched.GrantID = grantObj.ID
- }
- }
- result = append(result, enriched)
- }
- return result, total, nil
- }
- // GetUserJITStatus - gets JIT status for a user on a network
- func GetUserJITStatus(networkID, userID string) (*JITStatusResponse, error) {
- ctx := db.WithContext(context.Background())
- response := &JITStatusResponse{}
- // Check for active grant
- hasAccess, grant, err := CheckJITAccess(networkID, userID)
- if err != nil {
- return nil, err
- }
- response.HasAccess = hasAccess
- response.Grant = grant
- // Check for pending request
- request := schema.JITRequest{
- NetworkID: networkID,
- UserID: userID,
- }
- pendingRequests, err := request.ListPendingByNetwork(ctx)
- if err == nil {
- for _, req := range pendingRequests {
- if req.UserID == userID {
- response.PendingRequest = true
- response.Request = &req
- break
- }
- }
- }
- return response, nil
- }
- // UserJITNetworkStatus - represents JIT status for a network from user's perspective
- type UserJITNetworkStatus struct {
- NetworkID string `json:"network_id"`
- NetworkName string `json:"network_name,omitempty"`
- JITEnabled bool `json:"jit_enabled"`
- HasAccess bool `json:"has_access"`
- Grant *schema.JITGrant `json:"grant,omitempty"`
- Request *schema.JITRequest `json:"request,omitempty"`
- PendingRequest bool `json:"pending_request"`
- }
- // isUserAdminForNetwork - checks if user is super admin, admin, network admin, or global network admin
- func isUserAdminForNetwork(user *models.User, networkID string) bool {
- // Check platform role (super admin or admin)
- if user.PlatformRoleID == models.SuperAdminRole || user.PlatformRoleID == models.AdminRole {
- return true
- }
- if user.PlatformRoleID != models.PlatformUser {
- return false
- }
- networkIDModel := models.NetworkID(networkID)
- allNetworksID := models.AllNetworks
- globalNetworksAdminRoleID := models.UserRoleID(fmt.Sprintf("global-%s", models.NetworkAdmin))
- // Check user groups for network admin roles
- for groupID := range user.UserGroups {
- groupData, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, groupID.String())
- if err != nil {
- continue
- }
- var group models.UserGroup
- if err := json.Unmarshal([]byte(groupData), &group); err != nil {
- continue
- }
- // Check if group has network admin role for this network
- if roles, ok := group.NetworkRoles[networkIDModel]; ok {
- for roleID := range roles {
- if roleID == models.NetworkAdmin {
- return true
- }
- }
- }
- // Check if group has global network admin role
- if roles, ok := group.NetworkRoles[allNetworksID]; ok {
- for roleID := range roles {
- if roleID == models.NetworkAdmin || roleID == globalNetworksAdminRoleID {
- return true
- }
- }
- }
- }
- return false
- }
- // GetUserJITNetworksStatus - gets JIT status for multiple networks for a user
- func GetUserJITNetworksStatus(networks []models.Network, userID string) ([]UserJITNetworkStatus, error) {
- ctx := db.WithContext(context.Background())
- var result []UserJITNetworkStatus
- // Get user to check admin status
- user, err := logic.GetUser(userID)
- if err != nil {
- return nil, fmt.Errorf("failed to get user: %w", err)
- }
- for _, network := range networks {
- status := UserJITNetworkStatus{
- NetworkID: network.NetID,
- NetworkName: network.NetID, // Can be enhanced with network display name if available
- JITEnabled: network.JITEnabled == "yes",
- HasAccess: false,
- PendingRequest: false,
- }
- // Check if user is admin - if so, show JIT as disabled and has access
- if isUserAdminForNetwork(user, network.NetID) {
- status.JITEnabled = false
- status.HasAccess = true
- result = append(result, status)
- continue
- }
- // Only check JIT status if JIT is enabled on the network
- if status.JITEnabled {
- // Check for active grant
- hasAccess, grant, err := CheckJITAccess(network.NetID, userID)
- if err != nil {
- slog.Warn("failed to check JIT access", "network", network.NetID, "user", userID, "error", err)
- // Continue with default values
- } else {
- status.HasAccess = hasAccess
- status.Grant = grant
- }
- // Check for pending request
- request := schema.JITRequest{
- NetworkID: network.NetID,
- UserID: userID,
- }
- pendingRequests, err := request.ListPendingByNetwork(ctx)
- if err == nil {
- for _, req := range pendingRequests {
- if req.UserID == userID {
- status.PendingRequest = true
- status.Request = &req
- break
- }
- }
- }
- } else {
- // JIT not enabled, user has access
- status.HasAccess = true
- }
- result = append(result, status)
- }
- return result, nil
- }
- // ExpireJITGrants - expires grants that have passed their expiration time
- func ExpireJITGrants() error {
- ctx := db.WithContext(context.Background())
- grant := schema.JITGrant{}
- expiredGrants, err := grant.ListExpired(ctx)
- if err != nil {
- return fmt.Errorf("failed to list expired grants: %w", err)
- }
- for _, expiredGrant := range expiredGrants {
- var request *schema.JITRequest
- // Update associated request status to "expired" before deleting grant
- if expiredGrant.RequestID != "" {
- req := schema.JITRequest{ID: expiredGrant.RequestID}
- if err := req.Get(ctx); err == nil {
- request = &req
- // Only update if request is currently approved
- if request.Status == "approved" {
- request.Status = "expired"
- if err := request.Update(ctx); err != nil {
- slog.Warn("failed to update request status when expiring grant",
- "grant_id", expiredGrant.ID, "request_id", expiredGrant.RequestID, "error", err)
- // Don't fail the operation, just log
- }
- }
- }
- }
- // Disconnect user's ext clients from the network
- if err := disconnectUserExtClients(expiredGrant.NetworkID, expiredGrant.UserID); err != nil {
- slog.Error("failed to disconnect ext clients for expired grant",
- "grant_id", expiredGrant.ID, "user_id", expiredGrant.UserID, "error", err)
- }
- // Delete the expired grant
- if err := expiredGrant.Delete(ctx); err != nil {
- slog.Error("failed to delete expired grant", "grant_id", expiredGrant.ID, "error", err)
- continue
- }
- logger.Log(1, fmt.Sprintf("Expired and deleted JIT grant %s for user %s on network %s",
- expiredGrant.ID, expiredGrant.UserID, expiredGrant.NetworkID))
- }
- return nil
- }
- // DisconnectExtClientsFromNetwork - disconnects all ext clients from a network
- func DisconnectExtClientsFromNetwork(networkID string) error {
- extClients, err := logic.GetNetworkExtClients(networkID)
- if err != nil {
- return fmt.Errorf("failed to get ext clients: %w", err)
- }
- for _, client := range extClients {
- if err := logic.DeleteExtClient(client.Network, client.ClientID, false); err != nil {
- slog.Warn("failed to delete ext client when disabling JIT",
- "client_id", client.ClientID, "network", networkID, "error", err)
- continue
- }
- // DeleteExtClient handles MQ notifications internally
- }
- return nil
- }
- // GetNetworkAdmins - gets all network admins for a network
- func GetNetworkAdmins(networkID string) ([]models.User, error) {
- var admins []models.User
- users, err := logic.GetUsersDB()
- if err != nil {
- return admins, fmt.Errorf("failed to get users: %w", err)
- }
- networkIDModel := models.NetworkID(networkID)
- allNetworksID := models.AllNetworks
- for _, user := range users {
- isAdmin := false
- // Check platform role (super admin or admin)
- if user.PlatformRoleID == models.SuperAdminRole || user.PlatformRoleID == models.AdminRole {
- isAdmin = true
- }
- // Check network-specific roles
- if roles, ok := user.NetworkRoles[networkIDModel]; ok {
- for roleID := range roles {
- if roleID == models.NetworkAdmin {
- isAdmin = true
- break
- }
- }
- }
- // Check all-networks role
- globalNetworksAdminRoleID := models.UserRoleID(fmt.Sprintf("global-%s", models.NetworkAdmin))
- if roles, ok := user.NetworkRoles[allNetworksID]; ok {
- for roleID := range roles {
- if roleID == models.NetworkAdmin || roleID == globalNetworksAdminRoleID {
- isAdmin = true
- break
- }
- }
- }
- // Check user groups
- for groupID := range user.UserGroups {
- groupData, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, groupID.String())
- if err != nil {
- continue
- }
- var group models.UserGroup
- if err := json.Unmarshal([]byte(groupData), &group); err != nil {
- continue
- }
- // Check if group has network admin role for this network
- if roles, ok := group.NetworkRoles[networkIDModel]; ok {
- for roleID := range roles {
- if roleID == models.NetworkAdmin {
- isAdmin = true
- break
- }
- }
- }
- if roles, ok := group.NetworkRoles[allNetworksID]; ok {
- for roleID := range roles {
- if roleID == models.NetworkAdmin || roleID == globalNetworksAdminRoleID {
- isAdmin = true
- break
- }
- }
- }
- }
- if isAdmin {
- admins = append(admins, user)
- }
- }
- return admins, nil
- }
- // Helper functions
- func deactivateUserGrants(ctx context.Context, networkID, userID string) error {
- return DeactivateUserGrantsOnNetwork(networkID, userID)
- }
- // DeactivateUserGrantsOnNetwork - deletes all active grants for a user on a network
- func DeactivateUserGrantsOnNetwork(networkID, userID string) error {
- ctx := db.WithContext(context.Background())
- grant := schema.JITGrant{
- NetworkID: networkID,
- UserID: userID,
- }
- grants, err := grant.ListByUserAndNetwork(ctx)
- if err != nil {
- return err
- }
- for _, g := range grants {
- // Only delete grants that haven't expired yet (active grants)
- if time.Now().UTC().Before(g.ExpiresAt) {
- if err := g.Delete(ctx); err != nil {
- return fmt.Errorf("failed to delete grant %s: %w", g.ID, err)
- }
- }
- }
- return nil
- }
- // DisconnectUserExtClientsFromNetwork - disconnects a specific user's ext clients from a network
- func DisconnectUserExtClientsFromNetwork(networkID, userID string) error {
- return disconnectUserExtClients(networkID, userID)
- }
- func disconnectUserExtClients(networkID, userID string) error {
- extClients, err := logic.GetNetworkExtClients(networkID)
- if err != nil {
- return err
- }
- for _, client := range extClients {
- // Check if this ext client belongs to the user
- // Ext clients have OwnerID field that should match userID
- if client.OwnerID == userID {
- // Store original client for MQ notification
- clientCopy := client
- // Disable the ext client instead of deleting it
- // This preserves the client record so desktop apps can see the expiry status
- disabledClient, err := logic.ToggleExtClientConnectivity(&client, false)
- if err != nil {
- slog.Warn("failed to disable ext client", "client_id", client.ClientID, "error", err)
- continue
- }
- // Set JIT expiry to now to indicate revocation/expiry
- // This allows desktop apps to see the revocation when they poll the API
- now := time.Now().UTC()
- disabledClient.JITExpiresAt = &now
- if err := logic.SaveExtClient(&disabledClient); err != nil {
- slog.Warn("failed to update ext client expiry", "client_id", client.ClientID, "error", err)
- // Continue even if update fails
- }
- // Publish MQ peer update to notify ingress gateway nodes
- // This ensures nodes immediately remove the peer from WireGuard config
- if err := mq.PublishDeletedClientPeerUpdate(&clientCopy); err != nil {
- slog.Warn("failed to publish deleted client peer update",
- "client_id", client.ClientID, "error", err)
- // Don't fail the operation, just log
- }
- }
- }
- return nil
- }
|