refactor(console): rewrite /connectors/initiate to union scopes
The initiate handler now looks up the existing connector for the target (organization, provider) pair, reads its stored scope set through Connection.Scopes, and unions it with the scopes the caller passed in the query string. The union is what gets requested on the OAuth authorization URL, so reconnects never drop a previously granted scope. When an existing connector is found the handler also flags the flow as a reconnect via InitiateOptions.ConnectorID, so the OAuth2 state carries the id and the callback updates the row in place. When the provider supports it (Google Workspace), the auth URL also carries include_granted_scopes=true and the user sees only the delta on the consent screen. There is no short-circuit: every initiate click runs the full OAuth flow even if stored scopes already cover the request, because scope coverage is an unsafe proxy for token liveness. Revoked tokens or leftover connectors from deleted access sources would otherwise be silently reused. The handler body is extracted to its own file to keep NewMux readable. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
This commit is contained in:
@@ -97,61 +97,10 @@ func NewMux(
|
||||
|
||||
r.Handle("/graphql", graphqlHandler)
|
||||
|
||||
r.Get("/connectors/initiate", func(w http.ResponseWriter, r *http.Request) {
|
||||
provider := r.URL.Query().Get("provider")
|
||||
if provider == "" {
|
||||
httpserver.RenderError(w, http.StatusBadRequest, fmt.Errorf("missing provider parameter"))
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := connectorRegistry.Get(provider); err != nil {
|
||||
httpserver.RenderError(w, http.StatusBadRequest, fmt.Errorf("unsupported provider: %q", provider))
|
||||
return
|
||||
}
|
||||
|
||||
organizationID, err := gid.ParseGID(r.URL.Query().Get("organization_id"))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot parse organization id: %w", err))
|
||||
}
|
||||
|
||||
apiKey := authn.APIKeyFromContext(r.Context())
|
||||
if apiKey != nil {
|
||||
httpserver.RenderError(w, http.StatusBadRequest, fmt.Errorf("api key authentication cannot be used for this endpoint"))
|
||||
return
|
||||
}
|
||||
|
||||
identity := authn.IdentityFromContext(r.Context())
|
||||
if identity == nil {
|
||||
httpserver.RenderError(w, http.StatusUnauthorized, fmt.Errorf("authentication required"))
|
||||
return
|
||||
}
|
||||
session := authn.SessionFromContext(r.Context())
|
||||
if session == nil {
|
||||
httpserver.RenderError(w, http.StatusUnauthorized, fmt.Errorf("authentication required"))
|
||||
return
|
||||
}
|
||||
|
||||
if err := iamSvc.Authorizer.Authorize(r.Context(), iam.AuthorizeParams{
|
||||
Principal: identity.ID,
|
||||
Resource: organizationID,
|
||||
Session: &session.ID,
|
||||
Action: probo.ActionConnectorInitiate,
|
||||
}); err != nil {
|
||||
httpserver.RenderError(w, http.StatusForbidden, err)
|
||||
return
|
||||
}
|
||||
|
||||
opts := connector.InitiateOptions{
|
||||
Scopes: r.URL.Query()["scope"],
|
||||
}
|
||||
|
||||
redirectURL, err := connectorRegistry.Initiate(r.Context(), provider, organizationID, opts, r)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot initiate connector: %w", err))
|
||||
}
|
||||
|
||||
http.Redirect(w, r, redirectURL, http.StatusSeeOther)
|
||||
})
|
||||
r.Get(
|
||||
"/connectors/initiate",
|
||||
handleConnectorInitiate(logger, proboSvc, iamSvc, connectorRegistry),
|
||||
)
|
||||
|
||||
r.Get(
|
||||
"/connectors/complete",
|
||||
|
||||
Reference in New Issue
Block a user