diff --git a/packages/plugin-api/src/types.ts b/packages/plugin-api/src/types.ts index 534aba3a6f..300793278c 100644 --- a/packages/plugin-api/src/types.ts +++ b/packages/plugin-api/src/types.ts @@ -347,21 +347,12 @@ export interface OAuthFlowConfig { */ iosClientId?: string; /** - * Client ID for the web build (Google "Web application" client). - * Overrides `clientId` in the browser (non-Electron, non-native). - * The deployed origin's `/assets/oauth-callback.html` must be registered as an - * Authorized redirect URI on the Google Cloud Console OAuth client, and the - * deployed origin must be listed in Authorized JavaScript origins. + * Client ID for the web build, for providers that support public browser + * clients via Authorization Code + PKCE without a client secret. + * Overrides `clientId` in the browser (non-Electron, non-native) and omits + * `clientSecret`. */ webClientId?: string; - /** - * Client secret paired with `webClientId`, sent on the token exchange. - * Required for IdPs (e.g. Google "Web application" clients) that classify - * web clients as confidential and reject PKCE-only exchanges. Ships in - * client-side JS like the auth code itself — only set this if you accept - * that the secret is visible to anyone running the web build. - */ - webClientSecret?: string; scopes: string[]; /** Additional query parameters to append to the authorization URL (e.g. access_type, prompt). */ extraAuthParams?: Record; diff --git a/packages/plugin-dev/google-calendar-provider/src/plugin.spec.ts b/packages/plugin-dev/google-calendar-provider/src/plugin.spec.ts index ac61763ded..8c958267f7 100644 --- a/packages/plugin-dev/google-calendar-provider/src/plugin.spec.ts +++ b/packages/plugin-dev/google-calendar-provider/src/plugin.spec.ts @@ -16,6 +16,16 @@ beforeAll(async () => { }); describe('Google Calendar Plugin', () => { + describe('OAuth config', () => { + it('does not expose a browser web OAuth client', () => { + const oauthField = definition.configFields.find((field) => field.key === 'oauth'); + const oauthConfig = oauthField?.oauthConfig as Record; + + expect(oauthConfig.webClientId).toBeUndefined(); + expect(oauthConfig.webClientSecret).toBeUndefined(); + }); + }); + describe('fieldMappings', () => { const ctx = { issueId: 'event-1' }; @@ -309,7 +319,7 @@ describe('Google Calendar Plugin', () => { expect(body.start.date).toBeDefined(); expect(body.end.date).toBeDefined(); expect(body.start.dateTime).toBeUndefined(); - expect(result.issueId).toBe('new-event-1'); + expect(result.issueId).toBe('test-cal::new-event-1'); }); }); }); diff --git a/packages/plugin-dev/google-calendar-provider/src/plugin.ts b/packages/plugin-dev/google-calendar-provider/src/plugin.ts index 9159bba755..6badf4e971 100644 --- a/packages/plugin-dev/google-calendar-provider/src/plugin.ts +++ b/packages/plugin-dev/google-calendar-provider/src/plugin.ts @@ -37,20 +37,6 @@ const MOBILE_CLIENT_ID = // Requires "Custom URI scheme" to be enabled in Google Cloud Console. const IOS_CLIENT_ID = '637968426975-ka1muro7mee1go0m7hhog49fm7svr4os.apps.googleusercontent.com'; -// Web OAuth client — used by the browser build (app.super-productivity.com). -// Google classifies "Web application" clients as confidential, so the token -// endpoint rejects PKCE-only exchanges and we must ship the client_secret -// alongside the auth code. Unlike CLIENT_SECRET above (which is RFC 8252-style -// public for desktop apps), Google's policy formally requires this secret to -// be kept confidential — embedding it in client-side JS deviates from policy -// and the credential MAY be revoked by Google if abused. -// In practice the actual security boundary is PKCE + the registered redirect -// URI: a stolen secret cannot be used to redeem codes for a different origin. -// The deployed origin's /assets/oauth-callback.html must be registered as an -// Authorized redirect URI, and the origin itself in Authorized JavaScript origins. -const WEB_CLIENT_ID = - '637968426975-6t6q988dk0417re8pcl06vq75obqq944.apps.googleusercontent.com'; -const WEB_CLIENT_SECRET = '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'; // --- Config --- @@ -286,8 +272,8 @@ PluginAPI.registerIssueProvider({ clientSecret: CLIENT_SECRET, mobileClientId: MOBILE_CLIENT_ID, iosClientId: IOS_CLIENT_ID, - webClientId: WEB_CLIENT_ID, - webClientSecret: WEB_CLIENT_SECRET, + // Intentionally no webClientId: durable Google Calendar web OAuth + // requires a confidential client secret, which browser JS cannot keep. scopes: [CALENDAR_EVENTS_SCOPE, CALENDAR_READONLY_SCOPE], extraAuthParams: { access_type: 'offline', prompt: 'consent' }, }, diff --git a/src/app/features/issue/dialog-edit-issue-provider/dialog-edit-issue-provider.component.html b/src/app/features/issue/dialog-edit-issue-provider/dialog-edit-issue-provider.component.html index 3f4d2d3a77..c804cb9e4b 100644 --- a/src/app/features/issue/dialog-edit-issue-provider/dialog-edit-issue-provider.component.html +++ b/src/app/features/issue/dialog-edit-issue-provider/dialog-edit-issue-provider.component.html @@ -95,7 +95,20 @@ } @for (btn of oauthButtons; track btn.label) {
- @if (isOAuthConnected()) { + @if (isOAuthUnavailableInWeb(btn.oauthConfig)) { + + + info + {{ T.F.ISSUE.DIALOG.OAUTH_UNAVAILABLE_WEB | translate }} + + } @else if (isOAuthConnected()) {