(c *echo.Context)
| 28 | } |
| 29 | |
| 30 | func (s *Service) handleAuthorizeGet(c *echo.Context) error { |
| 31 | ctx := c.Request().Context() |
| 32 | |
| 33 | // Parse query parameters |
| 34 | responseType := c.QueryParam("response_type") |
| 35 | clientID := c.QueryParam("client_id") |
| 36 | redirectURI := c.QueryParam("redirect_uri") |
| 37 | state := c.QueryParam("state") |
| 38 | codeChallenge := c.QueryParam("code_challenge") |
| 39 | codeChallengeMethod := c.QueryParam("code_challenge_method") |
| 40 | |
| 41 | if responseType != "code" { |
| 42 | return oauth2Error(c, http.StatusBadRequest, "unsupported_response_type", "only 'code' response type is supported") |
| 43 | } |
| 44 | |
| 45 | if clientID == "" { |
| 46 | return oauth2Error(c, http.StatusBadRequest, "invalid_request", "client_id is required") |
| 47 | } |
| 48 | |
| 49 | client, err := s.store.GetOAuth2Client(ctx, clientID) |
| 50 | if err != nil { |
| 51 | return oauth2Error(c, http.StatusInternalServerError, "server_error", "failed to lookup client") |
| 52 | } |
| 53 | if client == nil { |
| 54 | return oauth2Error(c, http.StatusBadRequest, "invalid_client", "client not found") |
| 55 | } |
| 56 | |
| 57 | if redirectURI == "" { |
| 58 | return oauth2Error(c, http.StatusBadRequest, "invalid_request", "redirect_uri is required") |
| 59 | } |
| 60 | if !validateRedirectURI(redirectURI, client.Config.RedirectUris) { |
| 61 | return oauth2Error(c, http.StatusBadRequest, "invalid_redirect_uri", "redirect_uri not registered") |
| 62 | } |
| 63 | |
| 64 | // PKCE is required |
| 65 | if codeChallenge == "" { |
| 66 | return oauth2ErrorRedirect(c, redirectURI, state, "invalid_request", "code_challenge is required") |
| 67 | } |
| 68 | if codeChallengeMethod != "S256" { |
| 69 | return oauth2ErrorRedirect(c, redirectURI, state, "invalid_request", "code_challenge_method must be S256") |
| 70 | } |
| 71 | |
| 72 | // Redirect to frontend consent page. |
| 73 | // The frontend handles login if needed and binds consent to the user's |
| 74 | // currently active workspace (see handleAuthorizePost). |
| 75 | consentURL := fmt.Sprintf("/oauth2/consent?client_id=%s&redirect_uri=%s&state=%s&code_challenge=%s&code_challenge_method=%s", |
| 76 | url.QueryEscape(clientID), |
| 77 | url.QueryEscape(redirectURI), |
| 78 | url.QueryEscape(state), |
| 79 | url.QueryEscape(codeChallenge), |
| 80 | url.QueryEscape(codeChallengeMethod), |
| 81 | ) |
| 82 | return c.Redirect(http.StatusFound, consentURL) |
| 83 | } |
| 84 | |
| 85 | func (s *Service) handleAuthorizePost(c *echo.Context) error { |
| 86 | ctx := c.Request().Context() |
nothing calls this directly
no test coverage detected