1package coordinator
2
3import (
4 "strings"
5
6 "github.com/bnema/dumber/internal/infrastructure/webkit"
7)
8
9// oauthFlowPatterns are URL patterns that indicate an OAuth flow is in progress.
10// These appear in authorization URLs and callback redirects.
11var oauthFlowPatterns = []string{
12 // OAuth flow indicators
13 "oauth",
14 "authorize",
15 "authorization",
16 "auth/",
17 "/auth",
18 "login",
19 "signin",
20 "sign-in",
21 // OpenID Connect
22 "oidc",
23 "openid",
24 // Callback/redirect endpoints
25 "callback",
26 "redirect",
27 "/cb",
28}
29
30// oauthCallbackPatterns are URL query parameters that indicate an OAuth callback.
31// These appear when the OAuth provider redirects back to the application.
32var oauthCallbackPatterns = []string{
33 // Success parameters
34 "code=",
35 "access_token=",
36 "id_token=",
37 "token_type=",
38 "refresh_token=",
39 // Error parameters
40 "error=",
41 "error_description=",
42 "error_uri=",
43}
44
45// oauthRequestPatterns are query parameters found in OAuth authorization requests.
46var oauthRequestPatterns = []string{
47 "response_type=",
48 "client_id=",
49 "redirect_uri=",
50 "scope=",
51 "nonce=",
52}
53
54// IsOAuthURL checks if the URL is related to an OAuth flow.
55// This includes authorization endpoints, login pages, and callback URLs.
56func IsOAuthURL(url string) bool {
57 if url == "" {
58 return false
59 }
60 lower := strings.ToLower(url)
61
62 // Check for OAuth flow patterns in URL path
63 for _, pattern := range oauthFlowPatterns {
64 if strings.Contains(lower, pattern) {
65 return true
66 }
67 }
68
69 // Check for OAuth request parameters
70 for _, pattern := range oauthRequestPatterns {
71 if strings.Contains(lower, pattern) {
72 return true
73 }
74 }
75
76 return false
77}
78
79// IsOAuthCallback checks if the URL is an OAuth callback with response parameters.
80// This indicates the OAuth flow has completed (successfully or with error).
81func IsOAuthCallback(url string) bool {
82 if url == "" {
83 return false
84 }
85 lower := strings.ToLower(url)
86
87 // Must contain callback patterns (code=, access_token=, error=, etc.)
88 for _, pattern := range oauthCallbackPatterns {
89 if strings.Contains(lower, pattern) {
90 return true
91 }
92 }
93
94 return false
95}
96
97// ShouldAutoClose determines if a popup at this URL should auto-close.
98// Returns true for OAuth callbacks that indicate flow completion.
99//
100// This is comprehensive detection that handles:
101// - Success: code=, access_token=, id_token=
102// - Errors: error=, error_description=
103// - Various OAuth providers (Google, GitHub, Auth0, etc.)
104func ShouldAutoClose(url string) bool {
105 return IsOAuthCallback(url)
106}
107
108// shouldForceCloseOnSafetyTimeout determines if a popup should be force-closed
109// when the OAuth safety timeout is reached.
110//
111// Safety-timeout force close is disabled for stability. OAuth popups close on
112// callback detection or manual user action.
113func shouldForceCloseOnSafetyTimeout(url string) bool {
114 _ = url
115 return false
116}
117
118// IsOAuthSuccess checks if the callback indicates successful authentication.
119func IsOAuthSuccess(url string) bool {
120 if url == "" {
121 return false
122 }
123 lower := strings.ToLower(url)
124
125 // Check for success indicators
126 successPatterns := []string{
127 "code=",
128 "access_token=",
129 "id_token=",
130 }
131
132 for _, pattern := range successPatterns {
133 if strings.Contains(lower, pattern) {
134 // Make sure it's not an error response
135 if !strings.Contains(lower, "error=") {
136 return true
137 }
138 }
139 }
140
141 return false
142}
143
144// IsOAuthError checks if the callback indicates an authentication error.
145func IsOAuthError(url string) bool {
146 if url == "" {
147 return false
148 }
149 lower := strings.ToLower(url)
150 return strings.Contains(lower, "error=")
151}
152
153func composeOnClose(existing, next func()) func() {
154 if existing == nil {
155 return next
156 }
157 if next == nil {
158 return existing
159 }
160 return func() {
161 existing()
162 next()
163 }
164}
165
166func composeOnURIChanged(existing, next func(string)) func(string) {
167 if existing == nil {
168 return next
169 }
170 if next == nil {
171 return existing
172 }
173 return func(uri string) {
174 existing(uri)
175 next(uri)
176 }
177}
178
179func composeOnLoadChanged(
180 existing func(webkit.LoadEvent),
181 next func(webkit.LoadEvent),
182) func(webkit.LoadEvent) {
183 if existing == nil {
184 return next
185 }
186 if next == nil {
187 return existing
188 }
189 return func(event webkit.LoadEvent) {
190 existing(event)
191 next(event)
192 }
193}