From 26d086d1cf792c8dc4964d63120b7d0403ffdc90 Mon Sep 17 00:00:00 2001 From: Matt Brooker Date: Fri, 12 Jun 2026 16:44:21 -0400 Subject: [PATCH] feat(oauth): request explicit scopes instead of * on sign-in Mirror the scopes PostHog advertises as grantable (the API's OAUTH_SCOPES_SUPPORTED, served at /.well-known/oauth-authorization-server) instead of requesting the "*" wildcard, and bump OAUTH_SCOPE_VERSION 5->6 to force existing users to re-authorize with the narrower set. Keeps the desktop token least-privilege: no privileged or internal scopes, and newly-added scopes are not auto-granted. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/shared/src/oauth.test.ts | 182 +++++++++++++++++++++++++++- packages/shared/src/oauth.ts | 190 +++++++++++++++++++++++++++++- 2 files changed, 367 insertions(+), 5 deletions(-) diff --git a/packages/shared/src/oauth.test.ts b/packages/shared/src/oauth.test.ts index 0c94e061d4..f991b76731 100644 --- a/packages/shared/src/oauth.test.ts +++ b/packages/shared/src/oauth.test.ts @@ -8,9 +8,187 @@ describe("OAUTH_SCOPES guard", () => { scopes: OAUTH_SCOPES, }).toMatchInlineSnapshot(` { - "scopeVersion": 5, + "scopeVersion": 6, "scopes": [ - "*", + "openid", + "profile", + "email", + "action:read", + "action:write", + "access_control:read", + "access_control:write", + "account:read", + "account:write", + "activity_log:read", + "activity_log:write", + "alert:read", + "alert:write", + "annotation:read", + "annotation:write", + "approvals:read", + "approvals:write", + "batch_export:read", + "batch_export:write", + "batch_import:read", + "batch_import:write", + "business_knowledge:read", + "business_knowledge:write", + "cohort:read", + "cohort:write", + "comment:read", + "comment:write", + "conversation:read", + "conversation:write", + "customer_analytics:read", + "customer_analytics:write", + "customer_journey:read", + "customer_journey:write", + "customer_profile_config:read", + "customer_profile_config:write", + "dashboard:read", + "dashboard:write", + "event_filter:read", + "event_filter:write", + "dashboard_template:read", + "dashboard_template:write", + "dataset:read", + "dataset:write", + "desktop_recording:read", + "desktop_recording:write", + "early_access_feature:read", + "early_access_feature:write", + "endpoint:read", + "endpoint:write", + "engineering_analytics:read", + "engineering_analytics:write", + "error_tracking:read", + "error_tracking:write", + "evaluation:read", + "evaluation:write", + "element:read", + "element:write", + "event_definition:read", + "event_definition:write", + "experiment:read", + "experiment:write", + "experiment_saved_metric:read", + "experiment_saved_metric:write", + "export:read", + "export:write", + "external_data_schema:read", + "external_data_schema:write", + "external_data_source:read", + "external_data_source:write", + "feature_flag:read", + "feature_flag:write", + "file_system:read", + "file_system:write", + "file_system_shortcut:read", + "file_system_shortcut:write", + "group:read", + "group:write", + "health_issue:read", + "health_issue:write", + "heatmap:read", + "heatmap:write", + "hog_flow:read", + "hog_flow:write", + "hog_function:read", + "hog_function:write", + "insight:read", + "insight:write", + "insight_variable:read", + "insight_variable:write", + "integration:read", + "integration:write", + "legal_document:read", + "legal_document:write", + "link:read", + "link:write", + "live_debugger:read", + "live_debugger:write", + "llm_analytics:read", + "llm_analytics:write", + "llm_prompt:read", + "llm_prompt:write", + "llm_provider_key:read", + "llm_provider_key:write", + "llm_skill:read", + "llm_skill:write", + "logs:read", + "logs:write", + "marketing_analytics:read", + "marketing_analytics:write", + "notebook:read", + "notebook:write", + "organization:read", + "organization:write", + "organization_integration:read", + "organization_integration:write", + "organization_member:read", + "organization_member:write", + "person:read", + "person:write", + "persisted_folder:read", + "persisted_folder:write", + "plugin:read", + "plugin:write", + "product_tour:read", + "product_tour:write", + "project:read", + "project:write", + "property_definition:read", + "property_definition:write", + "query:read", + "query:write", + "replay_scanner:read", + "replay_scanner:write", + "revenue_analytics:read", + "revenue_analytics:write", + "session_recording:read", + "session_recording:write", + "session_recording_playlist:read", + "session_recording_playlist:write", + "sharing_configuration:read", + "sharing_configuration:write", + "signal_scout:read", + "signal_scout:write", + "streamlit_app:read", + "streamlit_app:write", + "subscription:read", + "subscription:write", + "survey:read", + "survey:write", + "tagger:read", + "tagger:write", + "ticket:read", + "ticket:write", + "task:read", + "task:write", + "tracing:read", + "tracing:write", + "field_note:read", + "field_note:write", + "uploaded_media:read", + "uploaded_media:write", + "usage_metric:read", + "usage_metric:write", + "user:read", + "user:write", + "user_interview:read", + "user_interview:write", + "visual_review:read", + "visual_review:write", + "warehouse_objects:read", + "warehouse_objects:write", + "warehouse_table:read", + "warehouse_table:write", + "warehouse_view:read", + "warehouse_view:write", + "web_analytics:read", + "web_analytics:write", + "webhook:read", + "webhook:write", ], } `); diff --git a/packages/shared/src/oauth.ts b/packages/shared/src/oauth.ts index b25eb5f6ac..68da34a0b3 100644 --- a/packages/shared/src/oauth.ts +++ b/packages/shared/src/oauth.ts @@ -4,10 +4,194 @@ export const POSTHOG_US_CLIENT_ID = "HCWoE0aRFMYxIxFNTTwkOORn5LBjOt2GVDzwSw5W"; export const POSTHOG_EU_CLIENT_ID = "AIvijgMS0dxKEmr5z6odvRd8Pkh5vts3nPTzgzU9"; export const POSTHOG_DEV_CLIENT_ID = "DC5uRLVbGI02YQ82grxgnK6Qn12SXWpCqdPb60oZ"; -// Bump OAUTH_SCOPE_VERSION below whenever OAUTH_SCOPES changes to force re-authentication -export const OAUTH_SCOPES = ["*"]; +// Mirrors the scopes PostHog advertises as grantable: OAUTH_SCOPES_SUPPORTED in the API's +// services/mcp/src/lib/oauth-scopes.generated.ts, published as scopes_supported at +// /.well-known/oauth-authorization-server. Requesting this explicit set instead of "*" keeps +// the token least-privilege (no privileged/internal scopes; new scopes are not auto-granted). +// Keep in sync with that generated list; bump OAUTH_SCOPE_VERSION below whenever it changes. +export const OAUTH_SCOPES = [ + "openid", + "profile", + "email", + "action:read", + "action:write", + "access_control:read", + "access_control:write", + "account:read", + "account:write", + "activity_log:read", + "activity_log:write", + "alert:read", + "alert:write", + "annotation:read", + "annotation:write", + "approvals:read", + "approvals:write", + "batch_export:read", + "batch_export:write", + "batch_import:read", + "batch_import:write", + "business_knowledge:read", + "business_knowledge:write", + "cohort:read", + "cohort:write", + "comment:read", + "comment:write", + "conversation:read", + "conversation:write", + "customer_analytics:read", + "customer_analytics:write", + "customer_journey:read", + "customer_journey:write", + "customer_profile_config:read", + "customer_profile_config:write", + "dashboard:read", + "dashboard:write", + "event_filter:read", + "event_filter:write", + "dashboard_template:read", + "dashboard_template:write", + "dataset:read", + "dataset:write", + "desktop_recording:read", + "desktop_recording:write", + "early_access_feature:read", + "early_access_feature:write", + "endpoint:read", + "endpoint:write", + "engineering_analytics:read", + "engineering_analytics:write", + "error_tracking:read", + "error_tracking:write", + "evaluation:read", + "evaluation:write", + "element:read", + "element:write", + "event_definition:read", + "event_definition:write", + "experiment:read", + "experiment:write", + "experiment_saved_metric:read", + "experiment_saved_metric:write", + "export:read", + "export:write", + "external_data_schema:read", + "external_data_schema:write", + "external_data_source:read", + "external_data_source:write", + "feature_flag:read", + "feature_flag:write", + "file_system:read", + "file_system:write", + "file_system_shortcut:read", + "file_system_shortcut:write", + "group:read", + "group:write", + "health_issue:read", + "health_issue:write", + "heatmap:read", + "heatmap:write", + "hog_flow:read", + "hog_flow:write", + "hog_function:read", + "hog_function:write", + "insight:read", + "insight:write", + "insight_variable:read", + "insight_variable:write", + "integration:read", + "integration:write", + "legal_document:read", + "legal_document:write", + "link:read", + "link:write", + "live_debugger:read", + "live_debugger:write", + "llm_analytics:read", + "llm_analytics:write", + "llm_prompt:read", + "llm_prompt:write", + "llm_provider_key:read", + "llm_provider_key:write", + "llm_skill:read", + "llm_skill:write", + "logs:read", + "logs:write", + "marketing_analytics:read", + "marketing_analytics:write", + "notebook:read", + "notebook:write", + "organization:read", + "organization:write", + "organization_integration:read", + "organization_integration:write", + "organization_member:read", + "organization_member:write", + "person:read", + "person:write", + "persisted_folder:read", + "persisted_folder:write", + "plugin:read", + "plugin:write", + "product_tour:read", + "product_tour:write", + "project:read", + "project:write", + "property_definition:read", + "property_definition:write", + "query:read", + "query:write", + "replay_scanner:read", + "replay_scanner:write", + "revenue_analytics:read", + "revenue_analytics:write", + "session_recording:read", + "session_recording:write", + "session_recording_playlist:read", + "session_recording_playlist:write", + "sharing_configuration:read", + "sharing_configuration:write", + "signal_scout:read", + "signal_scout:write", + "streamlit_app:read", + "streamlit_app:write", + "subscription:read", + "subscription:write", + "survey:read", + "survey:write", + "tagger:read", + "tagger:write", + "ticket:read", + "ticket:write", + "task:read", + "task:write", + "tracing:read", + "tracing:write", + "field_note:read", + "field_note:write", + "uploaded_media:read", + "uploaded_media:write", + "usage_metric:read", + "usage_metric:write", + "user:read", + "user:write", + "user_interview:read", + "user_interview:write", + "visual_review:read", + "visual_review:write", + "warehouse_objects:read", + "warehouse_objects:write", + "warehouse_table:read", + "warehouse_table:write", + "warehouse_view:read", + "warehouse_view:write", + "web_analytics:read", + "web_analytics:write", + "webhook:read", + "webhook:write", +]; -export const OAUTH_SCOPE_VERSION = 5; +export const OAUTH_SCOPE_VERSION = 6; // Token refresh settings export const TOKEN_REFRESH_BUFFER_MS = 30 * 60 * 1000; // 30 minutes before expiry