Skip to content

Commit 2fe6b73

Browse files
feat: add retry logic for flow lookups in Authentik
- Added getFlowWithRetry helper function to handle eventual consistency when looking up flows - Replaced direct GetFlow calls with retried lookups using 3 attempts and 2 second delay - Added debug logging to track retry attempts and flow UUID resolution - Improved error handling to gracefully handle temporary indexing delays in Authentik
1 parent 15bb791 commit 2fe6b73

1 file changed

Lines changed: 47 additions & 35 deletions

File tree

pkg/hecate/default_flows.go

Lines changed: 47 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"strings"
77
"text/template"
8+
"time"
89

910
"github.com/CodeMonkeyCybersecurity/eos/pkg/authentik"
1011
"github.com/CodeMonkeyCybersecurity/eos/pkg/eos_io"
@@ -199,41 +200,13 @@ func EnableDefaultFlows(rc *eos_io.RuntimeContext, cfg *DefaultFlowsConfig) erro
199200
zap.Error(err))
200201
} else {
201202
// CRITICAL: Brand API requires flow UUIDs, not slugs
202-
// Lookup each flow to get its PK (UUID)
203-
authFlow, err := client.GetFlow(rc.Ctx, fmt.Sprintf("%s-authentication", appSlug))
204-
if err != nil {
205-
logger.Warn("Failed to lookup authentication flow UUID",
206-
zap.String("slug", fmt.Sprintf("%s-authentication", appSlug)),
207-
zap.Error(err))
208-
}
209-
210-
enrollmentFlow, err := client.GetFlow(rc.Ctx, fmt.Sprintf("%s-enrollment", appSlug))
211-
if err != nil {
212-
logger.Warn("Failed to lookup enrollment flow UUID",
213-
zap.String("slug", fmt.Sprintf("%s-enrollment", appSlug)),
214-
zap.Error(err))
215-
}
216-
217-
invalidationGlobalFlow, err := client.GetFlow(rc.Ctx, fmt.Sprintf("%s-invalidation-global", appSlug))
218-
if err != nil {
219-
logger.Warn("Failed to lookup invalidation (global) flow UUID",
220-
zap.String("slug", fmt.Sprintf("%s-invalidation-global", appSlug)),
221-
zap.Error(err))
222-
}
223-
224-
recoveryFlow, err := client.GetFlow(rc.Ctx, fmt.Sprintf("%s-recovery", appSlug))
225-
if err != nil {
226-
logger.Warn("Failed to lookup recovery flow UUID",
227-
zap.String("slug", fmt.Sprintf("%s-recovery", appSlug)),
228-
zap.Error(err))
229-
}
230-
231-
unenrollmentFlow, err := client.GetFlow(rc.Ctx, fmt.Sprintf("%s-unenrollment", appSlug))
232-
if err != nil {
233-
logger.Warn("Failed to lookup unenrollment flow UUID",
234-
zap.String("slug", fmt.Sprintf("%s-unenrollment", appSlug)),
235-
zap.Error(err))
236-
}
203+
// Lookup each flow to get its PK (UUID) with retry logic for eventual consistency
204+
// RATIONALE: Freshly imported flows may not be immediately queryable due to Authentik indexing
205+
authFlow := getFlowWithRetry(rc, client, logger, fmt.Sprintf("%s-authentication", appSlug), 3, 2*time.Second)
206+
enrollmentFlow := getFlowWithRetry(rc, client, logger, fmt.Sprintf("%s-enrollment", appSlug), 3, 2*time.Second)
207+
invalidationGlobalFlow := getFlowWithRetry(rc, client, logger, fmt.Sprintf("%s-invalidation-global", appSlug), 3, 2*time.Second)
208+
recoveryFlow := getFlowWithRetry(rc, client, logger, fmt.Sprintf("%s-recovery", appSlug), 3, 2*time.Second)
209+
unenrollmentFlow := getFlowWithRetry(rc, client, logger, fmt.Sprintf("%s-unenrollment", appSlug), 3, 2*time.Second)
237210

238211
// Only update brand if we successfully looked up all flow UUIDs
239212
if authFlow != nil && enrollmentFlow != nil && invalidationGlobalFlow != nil && recoveryFlow != nil && unenrollmentFlow != nil {
@@ -989,3 +962,42 @@ entries:
989962
enabled: true
990963
timeout: 30
991964
`
965+
966+
// getFlowWithRetry attempts to retrieve a flow with retry logic for eventual consistency
967+
// RATIONALE: Freshly imported flows may not be immediately queryable in Authentik
968+
// This is a timing issue - the flow exists but the index hasn't updated yet
969+
func getFlowWithRetry(rc *eos_io.RuntimeContext, client *authentik.APIClient, logger otelzap.LoggerWithCtx, slug string, maxRetries int, retryDelay time.Duration) *authentik.FlowResponse {
970+
for attempt := 1; attempt <= maxRetries; attempt++ {
971+
flow, err := client.GetFlow(rc.Ctx, slug)
972+
if err != nil {
973+
logger.Warn("Failed to lookup flow UUID",
974+
zap.String("slug", slug),
975+
zap.Int("attempt", attempt),
976+
zap.Int("max_retries", maxRetries),
977+
zap.Error(err))
978+
} else if flow != nil {
979+
// Success - flow found
980+
logger.Debug("Found flow UUID",
981+
zap.String("slug", slug),
982+
zap.String("uuid", flow.PK),
983+
zap.Int("attempt", attempt))
984+
return flow
985+
}
986+
987+
// Flow not found yet (flow == nil, err == nil means "not found" per GetFlow contract)
988+
if attempt < maxRetries {
989+
logger.Debug("Flow not found yet, waiting before retry",
990+
zap.String("slug", slug),
991+
zap.Int("attempt", attempt),
992+
zap.Int("max_retries", maxRetries),
993+
zap.Duration("retry_delay", retryDelay))
994+
time.Sleep(retryDelay)
995+
}
996+
}
997+
998+
// All retries exhausted
999+
logger.Warn("Flow not found after all retries",
1000+
zap.String("slug", slug),
1001+
zap.Int("max_retries", maxRetries))
1002+
return nil
1003+
}

0 commit comments

Comments
 (0)