| package management |
|
|
| import ( |
| "errors" |
| "net/http" |
| "net/url" |
| "strings" |
|
|
| "github.com/gin-gonic/gin" |
| ) |
|
|
| type oauthCallbackRequest struct { |
| Provider string `json:"provider"` |
| RedirectURL string `json:"redirect_url"` |
| Code string `json:"code"` |
| State string `json:"state"` |
| Error string `json:"error"` |
| } |
|
|
| func (h *Handler) PostOAuthCallback(c *gin.Context) { |
| if h == nil || h.cfg == nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"status": "error", "error": "handler not initialized"}) |
| return |
| } |
|
|
| var req oauthCallbackRequest |
| if err := c.ShouldBindJSON(&req); err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"status": "error", "error": "invalid body"}) |
| return |
| } |
|
|
| canonicalProvider, err := NormalizeOAuthProvider(req.Provider) |
| if err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"status": "error", "error": "unsupported provider"}) |
| return |
| } |
|
|
| state := strings.TrimSpace(req.State) |
| code := strings.TrimSpace(req.Code) |
| errMsg := strings.TrimSpace(req.Error) |
|
|
| if rawRedirect := strings.TrimSpace(req.RedirectURL); rawRedirect != "" { |
| u, errParse := url.Parse(rawRedirect) |
| if errParse != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"status": "error", "error": "invalid redirect_url"}) |
| return |
| } |
| q := u.Query() |
| if state == "" { |
| state = strings.TrimSpace(q.Get("state")) |
| } |
| if code == "" { |
| code = strings.TrimSpace(q.Get("code")) |
| } |
| if errMsg == "" { |
| errMsg = strings.TrimSpace(q.Get("error")) |
| if errMsg == "" { |
| errMsg = strings.TrimSpace(q.Get("error_description")) |
| } |
| } |
| } |
|
|
| if state == "" { |
| c.JSON(http.StatusBadRequest, gin.H{"status": "error", "error": "state is required"}) |
| return |
| } |
| if err := ValidateOAuthState(state); err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"status": "error", "error": "invalid state"}) |
| return |
| } |
| if code == "" && errMsg == "" { |
| c.JSON(http.StatusBadRequest, gin.H{"status": "error", "error": "code or error is required"}) |
| return |
| } |
|
|
| sessionProvider, sessionStatus, ok := GetOAuthSession(state) |
| if !ok { |
| c.JSON(http.StatusNotFound, gin.H{"status": "error", "error": "unknown or expired state"}) |
| return |
| } |
| if sessionStatus != "" { |
| c.JSON(http.StatusConflict, gin.H{"status": "error", "error": "oauth flow is not pending"}) |
| return |
| } |
| if !strings.EqualFold(sessionProvider, canonicalProvider) { |
| c.JSON(http.StatusBadRequest, gin.H{"status": "error", "error": "provider does not match state"}) |
| return |
| } |
|
|
| if _, errWrite := WriteOAuthCallbackFileForPendingSession(h.cfg.AuthDir, canonicalProvider, state, code, errMsg); errWrite != nil { |
| if errors.Is(errWrite, errOAuthSessionNotPending) { |
| c.JSON(http.StatusConflict, gin.H{"status": "error", "error": "oauth flow is not pending"}) |
| return |
| } |
| c.JSON(http.StatusInternalServerError, gin.H{"status": "error", "error": "failed to persist oauth callback"}) |
| return |
| } |
|
|
| c.JSON(http.StatusOK, gin.H{"status": "ok"}) |
| } |
|
|