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 "state=",
52 "nonce=",
53}
54
55// IsOAuthURL checks if the URL is related to an OAuth flow.
56// This includes authorization endpoints, login pages, and callback URLs.
57func IsOAuthURL(url string) bool {
58 if url == "" {
59 return false
60 }
61 lower := strings.ToLower(url)
62
63 // Check for OAuth flow patterns in URL path
64 for _, pattern := range oauthFlowPatterns {
65 if strings.Contains(lower, pattern) {
66 return true
67 }
68 }
69
70 // Check for OAuth request parameters
71 for _, pattern := range oauthRequestPatterns {
72 if strings.Contains(lower, pattern) {
73 return true
74 }
75 }
76
77 return false
78}
79
80// IsOAuthCallback checks if the URL is an OAuth callback with response parameters.
81// This indicates the OAuth flow has completed (successfully or with error).
82func IsOAuthCallback(url string) bool {
83 if url == "" {
84 return false
85 }
86 lower := strings.ToLower(url)
87
88 // Must contain callback patterns (code=, access_token=, error=, etc.)
89 for _, pattern := range oauthCallbackPatterns {
90 if strings.Contains(lower, pattern) {
91 return true
92 }
93 }
94
95 return false
96}
97
98// ShouldAutoClose determines if a popup at this URL should auto-close.
99// Returns true for OAuth callbacks that indicate flow completion.
100//
101// This is comprehensive detection that handles:
102// - Success: code=, access_token=, id_token=
103// - Errors: error=, error_description=
104// - Various OAuth providers (Google, GitHub, Auth0, etc.)
105func ShouldAutoClose(url string) bool {
106 return IsOAuthCallback(url)
107}
108
109// IsOAuthSuccess checks if the callback indicates successful authentication.
110func IsOAuthSuccess(url string) bool {
111 if url == "" {
112 return false
113 }
114 lower := strings.ToLower(url)
115
116 // Check for success indicators
117 successPatterns := []string{
118 "code=",
119 "access_token=",
120 "id_token=",
121 }
122
123 for _, pattern := range successPatterns {
124 if strings.Contains(lower, pattern) {
125 // Make sure it's not an error response
126 if !strings.Contains(lower, "error=") {
127 return true
128 }
129 }
130 }
131
132 return false
133}
134
135// IsOAuthError checks if the callback indicates an authentication error.
136func IsOAuthError(url string) bool {
137 if url == "" {
138 return false
139 }
140 lower := strings.ToLower(url)
141 return strings.Contains(lower, "error=")
142}
143
144func composeOnClose(existing, next func()) func() {
145 if existing == nil {
146 return next
147 }
148 if next == nil {
149 return existing
150 }
151 return func() {
152 existing()
153 next()
154 }
155}
156
157func composeOnURIChanged(existing, next func(string)) func(string) {
158 if existing == nil {
159 return next
160 }
161 if next == nil {
162 return existing
163 }
164 return func(uri string) {
165 existing(uri)
166 next(uri)
167 }
168}
169
170func composeOnLoadChanged(
171 existing func(webkit.LoadEvent),
172 next func(webkit.LoadEvent),
173) func(webkit.LoadEvent) {
174 if existing == nil {
175 return next
176 }
177 if next == nil {
178 return existing
179 }
180 return func(event webkit.LoadEvent) {
181 existing(event)
182 next(event)
183 }
184}