CallbackHandler handles the OAuth callback, creates/updates the user, and creates a session.
(providerName string, db *gorm.DB, adminEmail, registrationMode, hmacSecret string)
| 157 | // CallbackHandler handles the OAuth callback, creates/updates the user, and |
| 158 | // creates a session. |
| 159 | func (m *OAuthManager) CallbackHandler(providerName string, db *gorm.DB, adminEmail, registrationMode, hmacSecret string) echo.HandlerFunc { |
| 160 | return func(c echo.Context) error { |
| 161 | provider, ok := m.providers[providerName] |
| 162 | if !ok { |
| 163 | return c.JSON(http.StatusNotFound, map[string]string{"error": "unknown provider"}) |
| 164 | } |
| 165 | |
| 166 | // Validate state |
| 167 | stateCookie, err := c.Cookie("oauth_state") |
| 168 | if err != nil || stateCookie.Value == "" || subtle.ConstantTimeCompare([]byte(stateCookie.Value), []byte(c.QueryParam("state"))) != 1 { |
| 169 | return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid OAuth state"}) |
| 170 | } |
| 171 | |
| 172 | // Clear state cookie |
| 173 | c.SetCookie(&http.Cookie{ |
| 174 | Name: "oauth_state", |
| 175 | Value: "", |
| 176 | Path: "/", |
| 177 | HttpOnly: true, |
| 178 | Secure: isSecure(c), |
| 179 | MaxAge: -1, |
| 180 | }) |
| 181 | |
| 182 | // Exchange code for token |
| 183 | code := c.QueryParam("code") |
| 184 | if code == "" { |
| 185 | return c.JSON(http.StatusBadRequest, map[string]string{"error": "missing authorization code"}) |
| 186 | } |
| 187 | |
| 188 | ctx, cancel := context.WithTimeout(c.Request().Context(), 30*time.Second) |
| 189 | defer cancel() |
| 190 | |
| 191 | token, err := provider.oauth2Config.Exchange(ctx, code) |
| 192 | if err != nil { |
| 193 | xlog.Error("OAuth code exchange failed", "provider", providerName, "error", err) |
| 194 | return c.JSON(http.StatusBadRequest, map[string]string{"error": "OAuth authentication failed"}) |
| 195 | } |
| 196 | |
| 197 | // Fetch user info — branch based on provider type |
| 198 | var userInfo *oauthUserInfo |
| 199 | if provider.oidcVerifier != nil { |
| 200 | userInfo, err = extractOIDCUserInfo(ctx, provider.oidcVerifier, token) |
| 201 | } else { |
| 202 | userInfo, err = fetchGitHubUserInfoAsOAuth(ctx, token.AccessToken) |
| 203 | } |
| 204 | if err != nil { |
| 205 | return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to fetch user info"}) |
| 206 | } |
| 207 | |
| 208 | // Retrieve invite code from cookie if present |
| 209 | var inviteCode string |
| 210 | if ic, err := c.Cookie("invite_code"); err == nil && ic.Value != "" { |
| 211 | inviteCode = ic.Value |
| 212 | // Clear the invite code cookie |
| 213 | c.SetCookie(&http.Cookie{ |
| 214 | Name: "invite_code", |
| 215 | Value: "", |
| 216 | Path: "/", |
no test coverage detected