Skip to content

fix: resolve GitHub OAuth integration bug chain#3355

Open
marcusgrando wants to merge 6 commits intodevfrom
fix/github-oauth-bugs
Open

fix: resolve GitHub OAuth integration bug chain#3355
marcusgrando wants to merge 6 commits intodevfrom
fix/github-oauth-bugs

Conversation

@marcusgrando
Copy link
Member

Summary

This PR fixes a chain of interconnected bugs in the GitHub OAuth integration flow that affected template deployment, account settings, and the import GitHub page. The bugs caused silent failures, missing error feedback, and a broken popup lifecycle.


Bug Analysis & Fixes

Bug 1 (ROOT CAUSE): GitHub integrations not loaded on engine-azion mount

Problem: When a template page with a VCS integration field (platform_feature__vcs_integration__uuid) was loaded for the first time, the GitHub integrations dropdown was empty. The user had to navigate away and come back (triggering the watch on props.schema) for integrations to appear.

Root cause: The onMounted hook in engine-azion.vue only called initializeForm() but never checked whether the schema contained a VCS field that required loading integrations. The integration loading logic (loadIntegrationOnShowButton) only existed inside the watch handler — meaning it only fired on schema changes, not on the initial mount.

Fix: After initializeForm() completes in onMounted, we now extract field names from inputSchema.value.groups and check if the VCS integration field is present. If so, loadIntegrationOnShowButton() is called immediately, loading the integrations list and registering the message event listener.

onMounted(async () => {
  await initializeForm()
  const groupsToCheck = inputSchema.value.groups || []
  const fieldNames = extractFieldNames(groupsToCheck)
  if (fieldNames.includes(vcsIntegrationFieldName.value)) {
    await loadIntegrationOnShowButton()
  }
})

Why this approach: We reuse the existing extractFieldNames + loadIntegrationOnShowButton functions — no new abstractions. The same pattern already worked correctly in the watch handler and in engine-jsonform.vue's onMounted, so this aligns engine-azion with the established convention.

Files: src/templates/template-engine-block/engine-azion.vue


Bug 2: Popup always sent integration-data regardless of callback type

Problem: The GitHubConnectionPopup component always posted a { event: 'integration-data', data: route.query } message to the opener window, regardless of whether the OAuth flow returned a code (authorization), an error (user denied access), or an installation_id (GitHub App installation without OAuth code).

This meant:

  • App installation callbacks (no code, just installation_id + setup_action) were incorrectly treated as OAuth authorization data, causing the parent to attempt postCallbackUrl with wrong parameters → HTTP 400.
  • Error callbacks (?error=access_denied&error_description=...) were also sent as integration-data, causing the parent to silently fail with no user feedback.

Fix: The popup now inspects route.query and dispatches one of three explicit events:

Condition Event Purpose
route.query.code exists integration-data OAuth authorization code → parent calls postCallbackUrl
route.query.error exists integration-error OAuth error → parent shows error toast
Neither integration-connected GitHub App installed → parent reloads integration list
if (route.query.code) {
  window.opener.postMessage({ event: 'integration-data', data: route.query }, platformUrl)
} else if (route.query.error) {
  window.opener.postMessage({ event: 'integration-error', data: route.query }, platformUrl)
} else {
  window.opener.postMessage({ event: 'integration-connected', data: route.query }, platformUrl)
}

Why this approach: The three GitHub OAuth/App callback shapes are well-documented and mutually exclusive. Using explicit event names makes the contract between popup and parent unambiguous and easy to debug. No new dependencies — just branching on existing query parameters.

Files: src/views/GitHubConnectionPopup/index.vue


Bug 3: Toast error used error.detail instead of error.message

Problem: In 4 files, the showWithOptions callback accessed error.detail to build the toast summary:

summary: `GitHub integration failed: ${error.detail}`

However, the error objects from the API service layer expose the error text on error.message, not error.detail. This resulted in toast messages showing GitHub integration failed: undefined — providing zero useful information to the user.

Fix: Replace error.detail with error.message in all 4 occurrences.

Why this approach: The error objects are standard JavaScript Error instances (or extend them). The .message property is the canonical way to access error text per the ECMAScript specification. The .detail property does not exist on these objects.

Files:

  • src/templates/template-engine-block/engine-azion.vue
  • src/templates/template-engine-block/engine-jsonform.vue
  • src/views/ImportGitHub/FormFields/FormFieldsImportGithub.vue
  • src/views/AccountSettings/FormFields/FormFieldsAccountSettings.vue

Bug 4: Popup used window.parent.close() instead of window.close()

Problem: The popup called window.parent.close() after posting the message. Since the popup is opened via window.open() (not an iframe), window.parent refers to the popup's own window — but semantically this is incorrect and can behave unexpectedly across browsers. In some contexts, window.parent.close() may be blocked by the browser's popup policy.

Fix: Changed to window.close(), which is the correct API for a popup window to close itself.

Files: src/views/GitHubConnectionPopup/index.vue


Hardening: Origin validation on message listeners

Problem: All 4 message listener handlers accepted postMessage events from any origin. This is a security vulnerability — a malicious page could send crafted messages to the console-kit window and trigger integration saves or list reloads.

Fix: Every message event handler now starts with an origin guard:

if (event.origin !== window.location.origin) return

This ensures only messages from the same origin (the platform itself, including the popup) are processed.

Why window.location.origin: The popup is always on the same domain as the parent. The popup already uses window.location.origin as the targetOrigin parameter in postMessage, so this is a symmetric check.

Files:

  • src/templates/template-engine-block/engine-azion.vue
  • src/templates/template-engine-block/engine-jsonform.vue
  • src/views/ImportGitHub/FormFields/FormFieldsImportGithub.vue
  • src/views/AccountSettings/FormFields/FormFieldsAccountSettings.vue

Hardening: Listeners now handle all 3 popup event types

Problem: The message event handlers only checked for integration-data. After the popup fix (Bug 2), the popup can now send integration-connected and integration-error events, but without updating the listeners, these events would be silently ignored.

Fix: All 4 listener handlers now respond to all 3 events:

  • integration-data → call saveIntegration(event.data) (existing behavior)
  • integration-connected → reload the integrations list (fixes "Add GitHub Account" when already installed)
  • integration-error → show error toast to the user

Files: Same 4 listener files as above.


Test Plan

Automated tests added

  • src/tests/templates/template-engine-block/engine-azion-mount-integrations.test.js

    • Verifies listIntegrations is called on mount when VCS field exists in schema groups
    • Verifies listIntegrations is NOT called when VCS field is absent
  • src/tests/views/GitHubConnectionPopup/index.test.js

    • Verifies integration-data event sent when code query param exists
    • Verifies integration-connected event sent when no code and no error
    • Verifies integration-error event sent when error query param exists
    • Verifies window.close() is called after posting message

Manual verification checklist

  • Template page with existing GitHub integration shows dropdown on first load
  • "Add GitHub Account" when already installed refreshes integrations without 400
  • Fresh OAuth flow with code persists integration and refreshes list
  • Provider denial/error path shows readable error toast (not undefined)
  • Popup closes after callback message
  • Message listeners ignore events from non-platform origins

@robsongajunior robsongajunior force-pushed the fix/github-oauth-bugs branch from a845220 to e0dc476 Compare March 10, 2026 13:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant