From ff65303a1aad6721a48467dddd644e9fb2e36e14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Wed, 21 Jan 2026 12:24:34 +0400 Subject: [PATCH] Fix documents signing authentication MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Émile Ré --- apps/console/src/routes.tsx | 41 ++--- pkg/server/api/console/v1/resolver.go | 214 +++++++++++++------------- 2 files changed, 131 insertions(+), 124 deletions(-) diff --git a/apps/console/src/routes.tsx b/apps/console/src/routes.tsx index 63ed1c803..7f57cfff6 100644 --- a/apps/console/src/routes.tsx +++ b/apps/console/src/routes.tsx @@ -104,29 +104,16 @@ const routes = [ }, { path: "/", - Component: lazy(() => import("./pages/iam/memberships/ViewerLayoutLoader")), - Fallback: ViewerLayoutLoading, ErrorBoundary: ErrorBoundary, children: [ { - index: true, - Component: lazy( - () => import("./pages/iam/memberships/MembershipsPageLoader"), - ), - }, - { - Component: CenteredLayout, + Component: lazy(() => import("./pages/iam/memberships/ViewerLayoutLoader")), + Fallback: ViewerLayoutLoading, children: [ { - path: "organizations/new", + index: true, Component: lazy( - () => import("./pages/iam/organizations/NewOrganizationPage"), - ), - }, - { - path: "documents/signing-requests", - Component: lazy( - () => import("./pages/DocumentSigningRequestsPage"), + () => import("./pages/iam/memberships/MembershipsPageLoader"), ), }, { @@ -135,10 +122,28 @@ const routes = [ () => import("./pages/iam/apiKeys/APIKeysPageLoader"), ), }, - ], + { + Component: CenteredLayout, + children: [ + { + path: "organizations/new", + Component: lazy( + () => import("./pages/iam/organizations/NewOrganizationPage"), + ), + }, + ], + }, + ] }, ], }, + { + path: "documents/signing-requests", + ErrorBoundary: ErrorBoundary, + Component: lazy( + () => import("./pages/DocumentSigningRequestsPage"), + ), + }, { path: "/organizations/:organizationId/employee", Component: lazy( diff --git a/pkg/server/api/console/v1/resolver.go b/pkg/server/api/console/v1/resolver.go index bfd0463e0..f8fd6fa77 100644 --- a/pkg/server/api/console/v1/resolver.go +++ b/pkg/server/api/console/v1/resolver.go @@ -65,13 +65,116 @@ func NewMux( safeRedirect := &saferedirect.SafeRedirect{AllowedHost: baseURL.Host()} - r.Use(authn.NewSessionMiddleware(iamSvc, cookieConfig)) - r.Use(authn.NewAPIKeyMiddleware(iamSvc, tokenSecret)) - r.Use(authn.NewIdentityPresenceMiddleware()) - graphqlHandler := NewGraphQLHandler(iamSvc, proboSvc, customDomainCname, logger) - r.Handle("/graphql", graphqlHandler) + r.Group(func(r chi.Router) { + r.Use(authn.NewSessionMiddleware(iamSvc, cookieConfig)) + r.Use(authn.NewAPIKeyMiddleware(iamSvc, tokenSecret)) + r.Use(authn.NewIdentityPresenceMiddleware()) + + r.Handle("/graphql", graphqlHandler) + + r.Get("/connectors/initiate", func(w http.ResponseWriter, r *http.Request) { + provider := r.URL.Query().Get("provider") + if provider != "SLACK" { + httpserver.RenderError(w, http.StatusBadRequest, fmt.Errorf("unsupported 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 + } + + redirectURL, err := connectorRegistry.Initiate(r.Context(), provider, organizationID, r) + if err != nil { + panic(fmt.Errorf("cannot initiate connector: %w", err)) + } + + // Allow external redirects for Slack OAuth only for now + slackSafeRedirect := &saferedirect.SafeRedirect{AllowedHost: "slack.com"} + slackSafeRedirect.Redirect(w, r, redirectURL, "/", http.StatusSeeOther) + }) + + r.Get("/connectors/complete", 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 + } + + var connectorProvider coredata.ConnectorProvider + switch provider { + case "SLACK": + connectorProvider = coredata.ConnectorProviderSlack + default: + httpserver.RenderError(w, http.StatusBadRequest, fmt.Errorf("unsupported provider")) + return + } + + stateToken := r.URL.Query().Get("state") + if stateToken == "" { + httpserver.RenderError(w, http.StatusBadRequest, fmt.Errorf("missing state parameter")) + return + } + + connection, organizationID, err := connectorRegistry.Complete(r.Context(), provider, r) + if err != nil { + panic(fmt.Errorf("cannot complete connector: %w", err)) + } + + continueURL := r.URL.Query().Get("continue") + + svc := proboSvc.WithTenant(organizationID.TenantID()) + + _, err = svc.Connectors.Create( + r.Context(), + probo.CreateConnectorRequest{ + OrganizationID: *organizationID, + Provider: connectorProvider, + Protocol: coredata.ConnectorProtocol(connection.Type()), + Connection: connection, + }, + ) + if err != nil { + panic(fmt.Errorf("cannot create or update connector: %w", err)) + } + + if continueURL != "" { + safeRedirect.Redirect(w, r, continueURL, "/", http.StatusSeeOther) + } else { + redirectURL := baseURL.WithPath("/organizations/" + organizationID.String()).MustString() + safeRedirect.Redirect(w, r, redirectURL, "/", http.StatusSeeOther) + } + }) + }) r.Get( "/documents/signing-requests", @@ -192,107 +295,6 @@ func NewMux( }, ) - r.Get("/connectors/initiate", func(w http.ResponseWriter, r *http.Request) { - provider := r.URL.Query().Get("provider") - if provider != "SLACK" { - httpserver.RenderError(w, http.StatusBadRequest, fmt.Errorf("unsupported 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 - } - - redirectURL, err := connectorRegistry.Initiate(r.Context(), provider, organizationID, r) - if err != nil { - panic(fmt.Errorf("cannot initiate connector: %w", err)) - } - - // Allow external redirects for Slack OAuth only for now - slackSafeRedirect := &saferedirect.SafeRedirect{AllowedHost: "slack.com"} - slackSafeRedirect.Redirect(w, r, redirectURL, "/", http.StatusSeeOther) - }) - - r.Get("/connectors/complete", 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 - } - - var connectorProvider coredata.ConnectorProvider - switch provider { - case "SLACK": - connectorProvider = coredata.ConnectorProviderSlack - default: - httpserver.RenderError(w, http.StatusBadRequest, fmt.Errorf("unsupported provider")) - return - } - - stateToken := r.URL.Query().Get("state") - if stateToken == "" { - httpserver.RenderError(w, http.StatusBadRequest, fmt.Errorf("missing state parameter")) - return - } - - connection, organizationID, err := connectorRegistry.Complete(r.Context(), provider, r) - if err != nil { - panic(fmt.Errorf("cannot complete connector: %w", err)) - } - - continueURL := r.URL.Query().Get("continue") - - svc := proboSvc.WithTenant(organizationID.TenantID()) - - _, err = svc.Connectors.Create( - r.Context(), - probo.CreateConnectorRequest{ - OrganizationID: *organizationID, - Provider: connectorProvider, - Protocol: coredata.ConnectorProtocol(connection.Type()), - Connection: connection, - }, - ) - if err != nil { - panic(fmt.Errorf("cannot create or update connector: %w", err)) - } - - if continueURL != "" { - safeRedirect.Redirect(w, r, continueURL, "/", http.StatusSeeOther) - } else { - redirectURL := baseURL.WithPath("/organizations/" + organizationID.String()).MustString() - safeRedirect.Redirect(w, r, redirectURL, "/", http.StatusSeeOther) - } - }) - return r }