Files
chatwoot/config/routes.rb
Tanmay Deep Sharma 239c4dcb91 feat: MFA (#12290)
## Linear:
- https://github.com/chatwoot/chatwoot/issues/486

## Description
This PR implements Multi-Factor Authentication (MFA) support for user
accounts, enhancing security by requiring a second form of verification
during login. The feature adds TOTP (Time-based One-Time Password)
authentication with QR code generation and backup codes for account
recovery.

## Type of change

- [ ] New feature (non-breaking change which adds functionality)

## How Has This Been Tested?

- Added comprehensive RSpec tests for MFA controller functionality
- Tested MFA setup flow with QR code generation
- Verified OTP validation and backup code generation
- Tested login flow with MFA enabled/disabled

## Checklist:

- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules

---------

Co-authored-by: Pranav <pranav@chatwoot.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2025-09-18 20:19:24 +05:30

613 lines
21 KiB
Ruby

Rails.application.routes.draw do
# AUTH STARTS
mount_devise_token_auth_for 'User', at: 'auth', controllers: {
confirmations: 'devise_overrides/confirmations',
passwords: 'devise_overrides/passwords',
sessions: 'devise_overrides/sessions',
token_validations: 'devise_overrides/token_validations',
omniauth_callbacks: 'devise_overrides/omniauth_callbacks'
}, via: [:get, :post]
## renders the frontend paths only if its not an api only server
if ActiveModel::Type::Boolean.new.cast(ENV.fetch('CW_API_ONLY_SERVER', false))
root to: 'api#index'
else
root to: 'dashboard#index'
get '/app', to: 'dashboard#index'
get '/app/*params', to: 'dashboard#index'
get '/app/accounts/:account_id/settings/inboxes/new/twitter', to: 'dashboard#index', as: 'app_new_twitter_inbox'
get '/app/accounts/:account_id/settings/inboxes/new/microsoft', to: 'dashboard#index', as: 'app_new_microsoft_inbox'
get '/app/accounts/:account_id/settings/inboxes/new/instagram', to: 'dashboard#index', as: 'app_new_instagram_inbox'
get '/app/accounts/:account_id/settings/inboxes/new/:inbox_id/agents', to: 'dashboard#index', as: 'app_twitter_inbox_agents'
get '/app/accounts/:account_id/settings/inboxes/new/:inbox_id/agents', to: 'dashboard#index', as: 'app_email_inbox_agents'
get '/app/accounts/:account_id/settings/inboxes/new/:inbox_id/agents', to: 'dashboard#index', as: 'app_instagram_inbox_agents'
get '/app/accounts/:account_id/settings/inboxes/:inbox_id', to: 'dashboard#index', as: 'app_instagram_inbox_settings'
get '/app/accounts/:account_id/settings/inboxes/:inbox_id', to: 'dashboard#index', as: 'app_email_inbox_settings'
resource :widget, only: [:show]
namespace :survey do
resources :responses, only: [:show]
end
resource :slack_uploads, only: [:show]
end
get '/api', to: 'api#index'
namespace :api, defaults: { format: 'json' } do
namespace :v1 do
# ----------------------------------
# start of account scoped api routes
resources :accounts, only: [:create, :show, :update] do
member do
post :update_active_at
get :cache_keys
end
scope module: :accounts do
namespace :actions do
resource :contact_merge, only: [:create]
end
resource :bulk_actions, only: [:create]
resources :agents, only: [:index, :create, :update, :destroy] do
post :bulk_create, on: :collection
end
namespace :captain do
resources :assistants do
member do
post :playground
end
collection do
get :tools
end
resources :inboxes, only: [:index, :create, :destroy], param: :inbox_id
resources :scenarios
end
resources :assistant_responses
resources :bulk_actions, only: [:create]
resources :copilot_threads, only: [:index, :create] do
resources :copilot_messages, only: [:index, :create]
end
resources :documents, only: [:index, :show, :create, :destroy]
end
resource :saml_settings, only: [:show, :create, :update, :destroy]
resources :agent_bots, only: [:index, :create, :show, :update, :destroy] do
delete :avatar, on: :member
post :reset_access_token, on: :member
end
resources :contact_inboxes, only: [] do
collection do
post :filter
end
end
resources :assignable_agents, only: [:index]
resource :audit_logs, only: [:show]
resources :callbacks, only: [] do
collection do
post :register_facebook_page
get :register_facebook_page
post :facebook_pages
post :reauthorize_page
end
end
resources :canned_responses, only: [:index, :create, :update, :destroy]
resources :automation_rules, only: [:index, :create, :show, :update, :destroy] do
post :clone
end
resources :macros, only: [:index, :create, :show, :update, :destroy] do
post :execute, on: :member
end
resources :sla_policies, only: [:index, :create, :show, :update, :destroy]
resources :custom_roles, only: [:index, :create, :show, :update, :destroy]
resources :agent_capacity_policies, only: [:index, :create, :show, :update, :destroy] do
scope module: :agent_capacity_policies do
resources :users, only: [:index, :create, :destroy]
resources :inbox_limits, only: [:create, :update, :destroy]
end
end
resources :campaigns, only: [:index, :create, :show, :update, :destroy]
resources :dashboard_apps, only: [:index, :show, :create, :update, :destroy]
namespace :channels do
resource :twilio_channel, only: [:create]
end
resources :conversations, only: [:index, :create, :show, :update, :destroy] do
collection do
get :meta
get :search
post :filter
end
scope module: :conversations do
resources :messages, only: [:index, :create, :destroy, :update] do
member do
post :translate
post :retry
end
end
resources :assignments, only: [:create]
resources :labels, only: [:create, :index]
resource :participants, only: [:show, :create, :update, :destroy]
resource :direct_uploads, only: [:create]
resource :draft_messages, only: [:show, :update, :destroy]
end
member do
post :mute
post :unmute
post :transcript
post :toggle_status
post :toggle_priority
post :toggle_typing_status
post :update_last_seen
post :unread
post :custom_attributes
get :attachments
get :inbox_assistant
end
end
resources :search, only: [:index] do
collection do
get :conversations
get :messages
get :contacts
get :articles
end
end
resources :contacts, only: [:index, :show, :update, :create, :destroy] do
collection do
get :active
get :search
post :filter
post :import
post :export
end
member do
get :contactable_inboxes
post :destroy_custom_attributes
delete :avatar
end
scope module: :contacts do
resources :conversations, only: [:index]
resources :contact_inboxes, only: [:create]
resources :labels, only: [:create, :index]
resources :notes
end
end
resources :csat_survey_responses, only: [:index] do
collection do
get :metrics
get :download
end
end
resources :applied_slas, only: [:index] do
collection do
get :metrics
get :download
end
end
resources :custom_attribute_definitions, only: [:index, :show, :create, :update, :destroy]
resources :custom_filters, only: [:index, :show, :create, :update, :destroy]
resources :inboxes, only: [:index, :show, :create, :update, :destroy] do
get :assignable_agents, on: :member
get :campaigns, on: :member
get :agent_bot, on: :member
post :set_agent_bot, on: :member
delete :avatar, on: :member
post :sync_templates, on: :member
end
resources :inbox_members, only: [:create, :show], param: :inbox_id do
collection do
delete :destroy
patch :update
end
end
resources :labels, only: [:index, :show, :create, :update, :destroy]
resources :notifications, only: [:index, :update, :destroy] do
collection do
post :read_all
get :unread_count
post :destroy_all
end
member do
post :snooze
post :unread
end
end
resource :notification_settings, only: [:show, :update]
resources :teams do
resources :team_members, only: [:index, :create] do
collection do
delete :destroy
patch :update
end
end
end
# Assignment V2 Routes
resources :assignment_policies do
resources :inboxes, only: [:index, :create, :destroy], module: :assignment_policies
end
resources :inboxes, only: [] do
resource :assignment_policy, only: [:show, :create, :destroy], module: :inboxes
end
namespace :twitter do
resource :authorization, only: [:create]
end
namespace :microsoft do
resource :authorization, only: [:create]
end
namespace :google do
resource :authorization, only: [:create]
end
namespace :instagram do
resource :authorization, only: [:create]
end
namespace :notion do
resource :authorization, only: [:create]
end
namespace :whatsapp do
resource :authorization, only: [:create]
end
resources :webhooks, only: [:index, :create, :update, :destroy]
namespace :integrations do
resources :apps, only: [:index, :show]
resources :hooks, only: [:show, :create, :update, :destroy] do
member do
post :process_event
end
end
resource :slack, only: [:create, :update, :destroy], controller: 'slack' do
member do
get :list_all_channels
end
end
resource :dyte, controller: 'dyte', only: [] do
collection do
post :create_a_meeting
post :add_participant_to_meeting
end
end
resource :shopify, controller: 'shopify', only: [:destroy] do
collection do
post :auth
get :orders
end
end
resource :linear, controller: 'linear', only: [] do
collection do
delete :destroy
get :teams
get :team_entities
post :create_issue
post :link_issue
post :unlink_issue
get :search_issue
get :linked_issues
end
end
resource :notion, controller: 'notion', only: [] do
collection do
delete :destroy
end
end
end
resources :working_hours, only: [:update]
resources :portals do
member do
patch :archive
delete :logo
post :send_instructions
get :ssl_status
end
resources :categories
resources :articles do
post :reorder, on: :collection
end
end
resources :upload, only: [:create]
end
end
# end of account scoped api routes
# ----------------------------------
namespace :integrations do
resources :webhooks, only: [:create]
end
resource :profile, only: [:show, :update] do
delete :avatar, on: :collection
member do
post :availability
post :auto_offline
put :set_active_account
post :resend_confirmation
post :reset_access_token
end
# MFA routes
scope module: 'profile' do
resource :mfa, controller: 'mfa', only: [:show, :create, :destroy] do
post :verify
post :backup_codes
end
end
end
resource :notification_subscriptions, only: [:create, :destroy]
namespace :widget do
resource :direct_uploads, only: [:create]
resource :config, only: [:create]
resources :campaigns, only: [:index]
resources :events, only: [:create]
resources :messages, only: [:index, :create, :update]
resources :conversations, only: [:index, :create] do
collection do
post :destroy_custom_attributes
post :set_custom_attributes
post :update_last_seen
post :toggle_typing
post :transcript
get :toggle_status
end
end
resource :contact, only: [:show, :update] do
collection do
post :destroy_custom_attributes
patch :set_user
end
end
resources :inbox_members, only: [:index]
resources :labels, only: [:create, :destroy]
namespace :integrations do
resource :dyte, controller: 'dyte', only: [] do
collection do
post :add_participant_to_meeting
end
end
end
end
end
namespace :v2 do
resources :accounts, only: [:create] do
scope module: :accounts do
resources :summary_reports, only: [] do
collection do
get :agent
get :team
get :inbox
get :label
end
end
resources :reports, only: [:index] do
collection do
get :summary
get :bot_summary
get :agents
get :inboxes
get :labels
get :teams
get :conversations
get :conversation_traffic
get :bot_metrics
end
end
resources :live_reports, only: [] do
collection do
get :conversation_metrics
get :grouped_conversation_metrics
end
end
end
end
end
end
if ChatwootApp.enterprise?
namespace :enterprise, defaults: { format: 'json' } do
namespace :api do
namespace :v1 do
resources :accounts do
member do
post :checkout
post :subscription
get :limits
post :toggle_deletion
end
end
end
end
post 'webhooks/stripe', to: 'webhooks/stripe#process_payload'
post 'webhooks/firecrawl', to: 'webhooks/firecrawl#process_payload'
end
end
# ----------------------------------------------------------------------
# Routes for platform APIs
namespace :platform, defaults: { format: 'json' } do
namespace :api do
namespace :v1 do
resources :users, only: [:create, :show, :update, :destroy] do
member do
get :login
post :token
end
end
resources :agent_bots, only: [:index, :create, :show, :update, :destroy] do
delete :avatar, on: :member
end
resources :accounts, only: [:index, :create, :show, :update, :destroy] do
resources :account_users, only: [:index, :create] do
collection do
delete :destroy
end
end
end
end
end
end
# ----------------------------------------------------------------------
# Routes for inbox APIs Exposed to contacts
namespace :public, defaults: { format: 'json' } do
namespace :api do
namespace :v1 do
resources :inboxes do
scope module: :inboxes do
resources :contacts, only: [:create, :show, :update] do
resources :conversations, only: [:index, :create, :show] do
member do
post :toggle_status
post :toggle_typing
post :update_last_seen
end
resources :messages, only: [:index, :create, :update]
end
end
end
end
resources :csat_survey, only: [:show, :update]
end
end
end
get 'hc/:slug', to: 'public/api/v1/portals#show'
get 'hc/:slug/sitemap.xml', to: 'public/api/v1/portals#sitemap'
get 'hc/:slug/:locale', to: 'public/api/v1/portals#show'
get 'hc/:slug/:locale/articles', to: 'public/api/v1/portals/articles#index'
get 'hc/:slug/:locale/categories', to: 'public/api/v1/portals/categories#index'
get 'hc/:slug/:locale/categories/:category_slug', to: 'public/api/v1/portals/categories#show'
get 'hc/:slug/:locale/categories/:category_slug/articles', to: 'public/api/v1/portals/articles#index'
get 'hc/:slug/articles/:article_slug.png', to: 'public/api/v1/portals/articles#tracking_pixel'
get 'hc/:slug/articles/:article_slug', to: 'public/api/v1/portals/articles#show'
# ----------------------------------------------------------------------
# Used in mailer templates
resource :app, only: [:index] do
resources :accounts do
resources :conversations, only: [:show]
end
end
# ----------------------------------------------------------------------
# Routes for channel integrations
mount Facebook::Messenger::Server, at: 'bot'
get 'webhooks/twitter', to: 'api/v1/webhooks#twitter_crc'
post 'webhooks/twitter', to: 'api/v1/webhooks#twitter_events'
post 'webhooks/line/:line_channel_id', to: 'webhooks/line#process_payload'
post 'webhooks/telegram/:bot_token', to: 'webhooks/telegram#process_payload'
post 'webhooks/sms/:phone_number', to: 'webhooks/sms#process_payload'
get 'webhooks/whatsapp/:phone_number', to: 'webhooks/whatsapp#verify'
post 'webhooks/whatsapp/:phone_number', to: 'webhooks/whatsapp#process_payload'
get 'webhooks/instagram', to: 'webhooks/instagram#verify'
post 'webhooks/instagram', to: 'webhooks/instagram#events'
namespace :twitter do
resource :callback, only: [:show]
end
namespace :linear do
resource :callback, only: [:show]
end
namespace :shopify do
resource :callback, only: [:show]
end
namespace :twilio do
resources :callback, only: [:create]
resources :delivery_status, only: [:create]
if ChatwootApp.enterprise?
resource :voice, only: [], controller: 'voice' do
collection do
post 'call/:phone', action: :call_twiml
post 'status/:phone', action: :status
end
end
end
end
get 'microsoft/callback', to: 'microsoft/callbacks#show'
get 'google/callback', to: 'google/callbacks#show'
get 'instagram/callback', to: 'instagram/callbacks#show'
get 'notion/callback', to: 'notion/callbacks#show'
# ----------------------------------------------------------------------
# Routes for external service verifications
get '.well-known/assetlinks.json' => 'android_app#assetlinks'
get '.well-known/apple-app-site-association' => 'apple_app#site_association'
get '.well-known/microsoft-identity-association.json' => 'microsoft#identity_association'
get '.well-known/cf-custom-hostname-challenge/:id', to: 'custom_domains#verify'
# ----------------------------------------------------------------------
# Internal Monitoring Routes
require 'sidekiq/web'
require 'sidekiq/cron/web'
devise_for :super_admins, path: 'super_admin', controllers: { sessions: 'super_admin/devise/sessions' }
devise_scope :super_admin do
get 'super_admin/logout', to: 'super_admin/devise/sessions#destroy'
namespace :super_admin do
root to: 'dashboard#index'
resource :app_config, only: [:show, :create]
# order of resources affect the order of sidebar navigation in super admin
resources :accounts, only: [:index, :new, :create, :show, :edit, :update, :destroy] do
post :seed, on: :member
post :reset_cache, on: :member
end
resources :users, only: [:index, :new, :create, :show, :edit, :update, :destroy] do
delete :avatar, on: :member, action: :destroy_avatar
end
resources :access_tokens, only: [:index, :show]
resources :installation_configs, only: [:index, :new, :create, :show, :edit, :update]
resources :agent_bots, only: [:index, :new, :create, :show, :edit, :update, :destroy] do
delete :avatar, on: :member, action: :destroy_avatar
end
resources :platform_apps, only: [:index, :new, :create, :show, :edit, :update, :destroy]
resource :instance_status, only: [:show]
resource :settings, only: [:show] do
get :refresh, on: :collection
end
# resources that doesn't appear in primary navigation in super admin
resources :account_users, only: [:new, :create, :show, :destroy]
end
authenticated :super_admin do
mount Sidekiq::Web => '/monitoring/sidekiq'
end
end
namespace :installation do
get 'onboarding', to: 'onboarding#index'
post 'onboarding', to: 'onboarding#create'
end
# ---------------------------------------------------------------------
# Routes for swagger docs
get '/swagger/*path', to: 'swagger#respond'
get '/swagger', to: 'swagger#respond'
# ----------------------------------------------------------------------
# Routes for testing
resources :widget_tests, only: [:index] unless Rails.env.production?
end