Skip to content

feat(oauth): validate callback URL origin / code / state in OAuth browser flow#13

Closed
NeritonDias wants to merge 74 commits intoEvolutionAPI:mainfrom
NeritonDias:feat/oauth-codex-v2
Closed

feat(oauth): validate callback URL origin / code / state in OAuth browser flow#13
NeritonDias wants to merge 74 commits intoEvolutionAPI:mainfrom
NeritonDias:feat/oauth-codex-v2

Conversation

@NeritonDias
Copy link
Copy Markdown

Summary

Hardens the OAuth Codex browser paste-URL flow introduced alongside the upcoming processor change (evo-ai-processor-community#2).

OAuthBrowserFlow.tsx

  • Parse the pasted callback URL with the native URL constructor instead of a substring match on 'code='. Malformed URLs are rejected with a clear user-facing message.
  • Reject URLs whose origin + pathname is not http://localhost:1455/auth/callback — the only redirect target the backend will exchange.
  • Surface OAuth provider errors (error, error_description query params) before attempting the token exchange.
  • Extract state from the authorize URL returned by auth-start, store it in component state, and compare against the state query param in the pasted callback URL. Defense in depth on top of the server-side validation added in the processor PR.
  • console.error / console.warn every failure path so production issues are diagnosable from the browser devtools, and preserve the upstream error message rather than swallowing it with a generic string.

ApiKeysModal.tsx

  • OAuthStatusBadge was being rendered with clientId={apiKey.id}, conflating the account / tenant id namespace with the API key id. Pull the real account id from useAppDataStore (same pattern already used in OpenAIModal.tsx) and pass it as clientId.

Test plan

  • Paste a callback URL with the wrong origin — UI rejects with the expected origin shown
  • Paste a callback URL with ?error=access_denied — UI shows the provider's error description
  • Paste a callback URL whose state does not match the one from auth-start — UI rejects before calling the backend
  • Happy path: paste a valid callback URL — auth-complete is called, status badge flips to 'Connected', and the rendered API key in the list shows the account's status badge
  • Regular (non-OAuth) OpenAI API key flow continues to work unchanged

Related PRs

daniloleonecarneiro and others added 30 commits March 18, 2026 16:32
- Added a new Setup page for creating the admin account.
- Integrated setup service to handle account creation.
- Updated routing to include the new setup page.
- Removed the old CreateAccount onboarding page.
- Updated various components to use a unified logo.
- Enhanced global configuration context to manage setup status.
- Added internationalization support for setup page in English and Portuguese.
- Updated RouterGuard to handle redirection based on setup status.
- Removed accountId references from ReconnectService and ActionCableService, simplifying connection logic.
- Updated PermissionsService to cache account permissions without accountId.
- Cleaned up various services (macros, notifications, pipelines, reports, scheduledActions, teams, users) by removing accountId comments and ensuring accountId is only used in headers.
- Introduced AppDataStore for managing application data, including account, agents, inboxes, labels, and teams with caching.
- Modified AuthStore to eliminate currentAccountId, relying on AppDataStore for account data management.
- Adjusted types across the application to remove accountId where unnecessary, ensuring a cleaner and more consistent codebase.
- Updated createCallbackPage utility to remove accountId from integration service callbacks.
Build and push multi-arch images (amd64 + arm64) in parallel to Docker Hub
on push to main and version tags. Uses SHA-pinned actions, provenance
attestation, and env-based variable passing to prevent script injection.
Story 2.3: Adds Test Connection button to SmtpConfig page with i18n
support (6 locales). Fixes adminConfigService to extract .configs from
API response wrapper. Tightens AdminConfigData type and removes as any casts.
- Implement TourFab button in the app header that auto-detects the
  current route and shows a tour trigger when a tour is registered
- Add WelcomeTourModal for onboarding new users on first access
- Create tourRegistry (pub/sub store) and matchTourRoute for
  route-based tour discovery
- Add useJoyride hook and JoyrideTooltip custom component
- Add i18n tour translations for pt-BR, pt, en, es, fr and it

Tours created (route → tourKey):
  /dashboard              → DashboardTour
  /chat                   → ChatTour
  /contacts               → ContactsTour
  /contacts/scheduled-actions → ScheduledActionsTour
  /pipelines              → PipelinesTour
  /agents/list            → AgentsTour
  /agents/custom-tools    → AgentsCustomToolsTour
  /agents/custom-mcp-servers → AgentsCustomMCPsTour
  /settings/account       → SettingsTour
  /settings/users         → SettingsAgentsTour
  /settings/teams         → SettingsTeamsTour
  /settings/labels        → SettingsLabelsTour
  /settings/attributes    → SettingsCustomAttributesTour
  /settings/canned-responses → SettingsCannedResponsesTour
  /settings/macros        → SettingsMacrosTour
  /settings/integrations  → SettingsIntegrationsTour
  /settings/access-tokens → SettingsAccessTokensTour
  /channels               → ChannelsTour
  /campaigns              → CampaignsTour
  /journeys               → JourneyTour

- Add data-tour attributes to all targeted page components
- Add searchDataTour prop to BaseHeader for granular search targeting
- Mount each tour component inside its own page following the
  existing AccountSettings pattern
Add Storage Configuration page to Admin Settings with provider selector
(Local/S3/S3-Compatible), conditional cloud fields, Zod validation,
masked secret handling, and Test Connection button. Includes route,
sidebar navigation, i18n translations (6 locales), and 12 unit tests.
- PaymentsConfig.tsx: form with Zod validation, masked secrets, toast feedback
- Route /settings/admin/payments with lazy loading
- Payments sidebar entry with CreditCard icon
- i18n translations for all 6 locales (en, pt-BR, pt, es, fr, it)
- 9 unit tests covering rendering, save, secret masking
- Review fixes: autoComplete="off", aria-label, empty→null for cleared secrets, deduplicated title
…App, Instagram)

Add admin settings page with tabbed UI for configuring Facebook, WhatsApp,
and Instagram channel credentials. Each tab saves independently via its
respective config type. Includes masked secret fields, boolean toggles,
read-only locked fields, Zod validation, i18n in 6 locales, and 19 tests.
Extend ChannelConfig with 3 new tabs for Evolution API, Evolution Go,
and Twitter credentials. Extract ChannelFormCard and TextField
components to reduce boilerplate. Add i18n translations for all 6
locales and 14 new tests.
…rompt templates

Admin panel page for configuring OpenAI API connection (URL, key, model,
audio transcription toggle) and 9 AI prompt templates (reply, summary,
rephrase, grammar, shorten, expand, friendly, formal, simplify).
Includes i18n for all 6 locales, route, sidebar nav, and unit tests.
…Slack)

Add admin settings page for configuring OAuth credentials for Linear,
HubSpot, Shopify, and Slack integrations. Each integration has independent
form sections with client ID and client secret fields, masked secret
handling, and independent save. Includes route, sidebar navigation with
Puzzle icon, i18n translations for all 6 locales, and 17 unit tests.
Add InboundEmailConfig page with provider selector (Relay/Mailgun/Mandrill/SendGrid),
conditional credential fields, Zod validation, and masked secrets.
Add route, MailOpen nav icon, and translations in all 6 locales.
Include 15 tests covering provider switching, validation, error handling, and secrets.
Create PushNotificationsConfig page with Firebase credentials (textarea),
project ID, iOS App ID, and Android Bundle ID fields.
Add route, Bell icon navigation, and translations in all 6 locales.
feat: Add guided tours system with Joyride across all main sections
Danilo Leone and others added 25 commits April 8, 2026 19:16
Replace shared privacy options with field-specific options to match
Evolution API and Evolution Go enum constraints (readreceipts, profile,
status, online, last, groupadd each have different allowed values).
WelcomeTourModal: Start Tour now saves onboarding:preference as guided and Skip saves it as solo, both persisted via the /user_tours API alongside the existing welcome dismissal key

useJoyride: tours no longer auto-start based solely on welcome dismissal; they now require onboarding:preference to be completed (guided choice) — users who chose explore alone will never see automatic tours on any section

TourFab: added data-tour attribute nav-tour-icon to the help button for future targeting

i18n: added soloHint keys (title + content) across all 6 supported languages (en, pt-BR, pt, es, fr, it)
…cards

- Create TooltipInfo component (HelpCircle icon + hover popover)
- Add optional tooltip prop to DashboardMetricCard, AreaChartCard,
  BarChartCard, DonutChartCard and OperationHeatmapCard
- Wire tour i18n copy (steps 2–20) as hover tooltips across
  DashboardMetricsSection, DashboardTrendsSection and
  DashboardPerformanceSection — reuses existing translations in all
  6 languages with no new keys required
- Fix TourFab: Ver tour desta página label now only appears after
  the page tour has been completed or skipped
feat(dashboard): add inline tooltips with tour copy to all dashboard …
setupService already sent form.crmExperience in the pre-login survey
payload, but the field was missing from the OnboardingFormData type, from
the form state, and from the UI. This caused a TypeScript build error and
meant that post-login submissions via surveyService were silently dropping
the same data even if the user had filled it in.

Adds the field to the type and initial state, renders a new SelectField
between the "biggest pain" and "main goal" questions using the existing
survey.crm i18n keys (already translated in all six locales), and bumps the
progress bar total from 6 to 7. Also teaches surveyService.toSnakeCase to
include crm_experience so both pre-login and post-login paths stay
consistent.
…d is disabled

On the new-channel flow, Facebook and Instagram cards are disabled when
canFB is false (FB app id / version not configured in admin settings), and
the WhatsApp Cloud provider card is disabled when canWpCloud is false.
Before this change, the cards were only visually dimmed with no hint about
why the user could not click them.

ChannelCard and ProviderGrid now accept an optional disabledTooltip
(a string for ChannelCard, a resolver function for ProviderGrid) and wrap
the disabled card in a Radix Tooltip that surfaces it on hover. The wrapper
is only rendered when both disabled and a tooltip are present, so enabled
cards keep their current behavior.

ChannelGrid passes a single i18n key for the Facebook/Instagram case, and
NewChannel passes the same key for whatsapp_cloud. The string is added to
all six locale files under channelGrid.notConfiguredTooltip and points the
user at Admin Settings > Channels (matching the real menu labels in
adminSettings.json).
…k-config-and-channel-tooltips

feat(channels): tooltip on disabled channel cards + onboarding crmExperience field
ModelSelector now calls the new core endpoint GET /agents/apikeys/:id/models
whenever the user picks an API key, and uses the returned list instead
of the hardcoded availableModels catalog. If the provider is one the
backend doesn't support dynamically (e.g. Perplexity, Bedrock, Vertex),
or the call fails, the component falls back to the existing hardcoded
list so nothing regresses.

Adds a loading state on the trigger button while the models are being
fetched and a new ApiKeyModelsResponse type to the agent types barrel.
The 'Test agent in chat' card on the wizard success step navigated to
the global /conversations inbox, where there is no way to test the
newly-created agent. Now it routes to the agent edit page with a
?test=1 query param, which auto-opens the AgentTestChat dialog — the
same drawer reachable from the edit header's Test button.
The agent name Badge in the AgentChatArea header sat at the top-right
of the content, overlapping the Radix dialog's close button. Add
right padding and a small gap so the badge sits to the left of the X.
Adds a 'Copy ID' entry (with toast feedback) to:
- Agents list actions dropdown
- Pipelines list table and card menus
- Pipeline kanban header, stage and item menus

Switches the pipeline Duplicate icon to CopyPlus so it stays visually
distinct from the Copy icon now used for Copy ID. Translation keys
added for all six locales.
PermissionsContext was flipping isReady true as soon as the user loaded
but before the account/user permissions fetch actually ran, so route
guards briefly evaluated can() against empty arrays and redirected to
/unauthorized — the user saw an 'Access Denied' page flash and disappear
right after logging in, especially after a hard reload.

Track whether both fetches have completed for the current user with
dedicated flags that reset on user change, and require them in isReady.
AppInitializer fetched /user_tours only inside the main init effect,
which short-circuits once isInitialized is true. On the first page
load of a logged-out user the effect marks itself initialized, so when
the user subsequently logs in the tours store (not persisted) stays
empty and the Welcome modal reappears even after being skipped.

Move the tour fetch into its own effect keyed on user.id so every
login (including after a hard reload) repopulates the store before
WelcomeTourModal and useJoyride evaluate their dismissed flags.
- ChannelConfig schemas enforce required essentials per integration with
  mode:'onChange'; Save disabled while invalid; inline errors only render
  after the first submit attempt (formState.submitCount>0).
- SecretField gains an error prop and a sentinel so secrets already stored
  in the DB satisfy required validation without exposing or retransmitting
  the value; buildPayload maps the sentinel back to null on save.
- Replace save-error toast with a persistent, selectable inline banner in
  ChannelFormCard so the admin can copy the backend error text.
- Invalidate the GlobalConfig cache after each save and broadcast to
  listeners so the channel creation flow reflects hasXxxConfig changes
  without a manual reload.
- useChannelForm reads hasFacebook/hasWhatsapp/hasInstagram/hasTwitter
  from the backend booleans; email/google and email/microsoft stay on
  their existing string gates.
- CloudWhatsappForm:
  - SDK effect now depends on wpAppId/wpApiVersion so it waits for the
    GlobalConfig response before calling FB.init.
  - Drop script.crossOrigin='anonymous' on the SDK script tag here, in
    FacebookChannelForm and in AuthorizationBanners; Facebook's CDN does
    not emit the CORS headers it requires and the script failed to load.
  - The FB embedded-signup button on NewChannel is now gated by the
    WhatsApp config (canWpCloud), because the SDK initializes with
    wpAppId/wpApiVersion and logs in with wpWhatsappConfigId.
- Add generic i18n keys: common:validation.required (6 locales),
  channels.dismissError for the banner close button, and
  cloudWhatsappForm.facebookIntegration.loadingSDK.
- WhatsAppForm under components/integrations/forms keeps two fields
  marked required as documentation for when that orphan directory is
  adopted; no page renders it today.
- restore DarkModeProvider with localStorage + prefers-color-scheme and re-render ThemeToggle in the header
- replace EVO_CRM.png with theme-aware AppLogo (dark/light SVG variants) across all 13 usages
- fix contrast on dashboard tone badges, BaseStatusBadge and channel icon wrapper in light mode
- rename "Operators" to "Team Members" in English locales to avoid collision with AI "Agents"
…mismatch)

- OAuthDeviceCodeFlow.tsx: prefix unused keyId with underscore
- integrations/main.ts: make auth_method optional in OpenAIFormData to match OpenAIConfig
OAuthBrowserFlow.tsx:
- Parse the pasted callback URL with the native URL constructor instead
  of a bare substring match on 'code='. Reject malformed URLs with a
  clear user-facing message.
- Check url.origin + url.pathname against the expected redirect target
  (http://localhost:1455/auth/callback) so a URL from the wrong origin
  cannot be forwarded to auth-complete.
- Surface OAuth provider errors (error, error_description query params)
  before attempting the token exchange.
- Extract 'state' from the authorize URL returned by auth-start, store
  it in component state and compare it against the 'state' query param
  in the pasted callback URL — defense in depth on top of the
  server-side check. A mismatch aborts the flow locally.
- console.error / console.warn every failure path so production
  issues are diagnosable from the browser devtools.
- Preserve the upstream error message when available rather than
  swallowing it with a generic string.

ApiKeysModal.tsx:
- OAuthStatusBadge was being rendered with clientId={apiKey.id}, which
  confused the 'account / tenant id' namespace with the API key id.
  Pull the real account id from useAppDataStore (same pattern as
  OpenAIModal.tsx) and pass it as clientId.
Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @NeritonDias, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants