diff --git a/.circleci/config.yml b/.circleci/config.yml index c67063ae6..cdddc4a06 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ defaults: &defaults working_directory: ~/build docker: # specify the version you desire here - - image: cimg/ruby:3.2.2-browsers + - image: cimg/ruby:3.3.3-browsers # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 17021d1e7..a804bb15c 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -12,7 +12,7 @@ services: args: VARIANT: "ubuntu-22.04" NODE_VERSION: "20.9.0" - RUBY_VERSION: "3.2.2" + RUBY_VERSION: "3.3.3" # On Linux, you may need to update USER_UID and USER_GID below if not your local UID is not 1000. USER_UID: "1000" USER_GID: "1000" @@ -25,7 +25,7 @@ services: args: VARIANT: "ubuntu-22.04" NODE_VERSION: "20.9.0" - RUBY_VERSION: "3.2.2" + RUBY_VERSION: "3.3.3" # On Linux, you may need to update USER_UID and USER_GID below if not your local UID is not 1000. USER_UID: "1000" USER_GID: "1000" diff --git a/.env.example b/.env.example index 26b1487ae..befcde463 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,9 @@ +# Learn about the various environment variables at +# https://www.chatwoot.com/docs/self-hosted/configuration/environment-variables/#rails-production-variables + # Used to verify the integrity of signed cookies. so ensure a secure value is set +# SECRET_KEY_BASE should be alphanumeric. Avoid special characters or symbols. +# Use `rake secret` to generate this variable SECRET_KEY_BASE=replace_with_lengthy_secure_hex # Replace with the URL you are planning to use for your app @@ -80,6 +85,8 @@ SMTP_OPENSSL_VERIFY_MODE=peer # Comment out the following environment variables if required by your SMTP server # SMTP_TLS= # SMTP_SSL= +# SMTP_OPEN_TIMEOUT +# SMTP_READ_TIMEOUT # Mail Incoming # This is the domain set for the reply emails when conversation continuity is enabled @@ -184,12 +191,6 @@ ANDROID_SHA256_CERT_FINGERPRINT=AC:73:8E:DE:EB:56:EA:CC:10:87:02:A7:65:37:7B:38: # SENTRY_DSN= -# MICROSOFT CLARITY -# MS_CLARITY_TOKEN=xxxxxxxxx - -# GOOGLE_TAG_MANAGER -# GOOGLE_TAG = GTM-XXXXXXX - ## Scout ## https://scoutapm.com/docs/ruby/configuration # SCOUT_KEY=YOURKEY diff --git a/.ruby-version b/.ruby-version index be94e6f53..619b53766 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.2.2 +3.3.3 diff --git a/Gemfile b/Gemfile index 1b2ed90b9..f690e6876 100644 --- a/Gemfile +++ b/Gemfile @@ -1,10 +1,10 @@ source 'https://rubygems.org' -ruby '3.2.2' +ruby '3.3.3' ##-- base gems for rails --## gem 'rack-cors', '2.0.0', require: 'rack/cors' -gem 'rails', '~> 7.0.8.1' +gem 'rails', '~> 7.0.8.4' # Reduces boot times through caching; required in config/boot.rb gem 'bootsnap', require: false @@ -61,7 +61,7 @@ gem 'redis-namespace' gem 'activerecord-import' ##--- gems for server & infra configuration ---## -gem 'dotenv-rails' +gem 'dotenv-rails', '>= 3.0.0' gem 'foreman' gem 'puma' gem 'webpacker' @@ -111,18 +111,18 @@ gem 'elastic-apm', require: false gem 'newrelic_rpm', require: false gem 'newrelic-sidekiq-metrics', '>= 1.6.2', require: false gem 'scout_apm', require: false -gem 'sentry-rails', '>= 5.14.0', require: false +gem 'sentry-rails', '>= 5.18.1', require: false gem 'sentry-ruby', require: false -gem 'sentry-sidekiq', '>= 5.15.0', require: false +gem 'sentry-sidekiq', '>= 5.18.1', require: false ##-- background job processing --## -gem 'sidekiq', '>= 7.2.4' +gem 'sidekiq', '>= 7.3.0' # We want cron jobs gem 'sidekiq-cron', '>= 1.12.0' ##-- Push notification service --## gem 'fcm' -gem 'web-push' +gem 'web-push', '>= 3.0.1' ##-- geocoding / parse location from ip --## # http://www.rubygeocoder.com/ @@ -228,7 +228,7 @@ group :development, :test do gem 'mock_redis' gem 'pry-rails' gem 'rspec_junit_formatter' - gem 'rspec-rails' + gem 'rspec-rails', '>= 6.1.3' gem 'rubocop', require: false gem 'rubocop-performance', require: false gem 'rubocop-rails', require: false diff --git a/Gemfile.lock b/Gemfile.lock index a226a3c3f..d3d405f63 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -33,70 +33,70 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (7.0.8.3) - actionpack (= 7.0.8.3) - activesupport (= 7.0.8.3) + actioncable (7.0.8.4) + actionpack (= 7.0.8.4) + activesupport (= 7.0.8.4) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (7.0.8.3) - actionpack (= 7.0.8.3) - activejob (= 7.0.8.3) - activerecord (= 7.0.8.3) - activestorage (= 7.0.8.3) - activesupport (= 7.0.8.3) + actionmailbox (7.0.8.4) + actionpack (= 7.0.8.4) + activejob (= 7.0.8.4) + activerecord (= 7.0.8.4) + activestorage (= 7.0.8.4) + activesupport (= 7.0.8.4) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.0.8.3) - actionpack (= 7.0.8.3) - actionview (= 7.0.8.3) - activejob (= 7.0.8.3) - activesupport (= 7.0.8.3) + actionmailer (7.0.8.4) + actionpack (= 7.0.8.4) + actionview (= 7.0.8.4) + activejob (= 7.0.8.4) + activesupport (= 7.0.8.4) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp rails-dom-testing (~> 2.0) - actionpack (7.0.8.3) - actionview (= 7.0.8.3) - activesupport (= 7.0.8.3) + actionpack (7.0.8.4) + actionview (= 7.0.8.4) + activesupport (= 7.0.8.4) rack (~> 2.0, >= 2.2.4) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (7.0.8.3) - actionpack (= 7.0.8.3) - activerecord (= 7.0.8.3) - activestorage (= 7.0.8.3) - activesupport (= 7.0.8.3) + actiontext (7.0.8.4) + actionpack (= 7.0.8.4) + activerecord (= 7.0.8.4) + activestorage (= 7.0.8.4) + activesupport (= 7.0.8.4) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.8.3) - activesupport (= 7.0.8.3) + actionview (7.0.8.4) + activesupport (= 7.0.8.4) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) active_record_query_trace (1.8) - activejob (7.0.8.3) - activesupport (= 7.0.8.3) + activejob (7.0.8.4) + activesupport (= 7.0.8.4) globalid (>= 0.3.6) - activemodel (7.0.8.3) - activesupport (= 7.0.8.3) - activerecord (7.0.8.3) - activemodel (= 7.0.8.3) - activesupport (= 7.0.8.3) + activemodel (7.0.8.4) + activesupport (= 7.0.8.4) + activerecord (7.0.8.4) + activemodel (= 7.0.8.4) + activesupport (= 7.0.8.4) activerecord-import (1.4.1) activerecord (>= 4.2) - activestorage (7.0.8.3) - actionpack (= 7.0.8.3) - activejob (= 7.0.8.3) - activerecord (= 7.0.8.3) - activesupport (= 7.0.8.3) + activestorage (7.0.8.4) + actionpack (= 7.0.8.4) + activejob (= 7.0.8.4) + activerecord (= 7.0.8.4) + activesupport (= 7.0.8.4) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (7.0.8.3) + activesupport (7.0.8.4) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -156,7 +156,7 @@ GEM msgpack (~> 1.2) brakeman (5.4.1) browser (5.3.1) - builder (3.2.4) + builder (3.3.0) bullet (7.0.7) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) @@ -169,7 +169,7 @@ GEM climate_control (1.2.0) coderay (1.1.3) commonmarker (0.23.10) - concurrent-ruby (1.2.3) + concurrent-ruby (1.3.3) connection_pool (2.4.1) crack (0.4.5) rexml @@ -183,13 +183,16 @@ GEM activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) - date (3.3.4) - ddtrace (1.11.1) - debase-ruby_core_source (>= 0.10.16, <= 3.2.0) - libdatadog (~> 2.0.0.1.0) - libddwaf (~> 1.8.2.0.0) + datadog-ci (0.8.3) msgpack - debase-ruby_core_source (3.2.0) + date (3.3.4) + ddtrace (1.23.2) + datadog-ci (~> 0.8.1) + debase-ruby_core_source (= 3.3.1) + libdatadog (~> 7.0.0.1.0) + libddwaf (~> 1.14.0.0.0) + msgpack + debase-ruby_core_source (3.3.1) debug (1.8.0) irb (>= 1.5.0) reline (>= 0.3.1) @@ -204,16 +207,16 @@ GEM bcrypt (~> 3.0) devise (> 3.5.2, < 5) rails (>= 4.2.0, < 7.2) - diff-lcs (1.5.0) + diff-lcs (1.5.1) digest-crc (0.6.4) rake (>= 12.0.0, < 14.0.0) docile (1.4.0) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) - dotenv (2.8.1) - dotenv-rails (2.8.1) - dotenv (= 2.8.1) - railties (>= 3.2) + dotenv (3.1.2) + dotenv-rails (3.1.2) + dotenv (= 3.1.2) + railties (>= 6.1) down (5.4.0) addressable (~> 2.8) ecma-re-validator (0.4.0) @@ -223,7 +226,7 @@ GEM http (>= 3.0) ruby2_keywords email_reply_trimmer (0.1.13) - erubi (1.12.0) + erubi (1.13.0) et-orbi (1.2.7) tzinfo execjs (2.8.1) @@ -316,10 +319,7 @@ GEM google-cloud-translate-v3 (0.6.0) gapic-common (>= 0.17.1, < 2.a) google-cloud-errors (~> 1.0) - google-protobuf (3.25.3) google-protobuf (3.25.3-arm64-darwin) - google-protobuf (3.25.3-x86_64-darwin) - google-protobuf (3.25.3-x86_64-linux) googleapis-common-protos (1.4.0) google-protobuf (~> 3.14) googleapis-common-protos-types (~> 1.2) @@ -335,18 +335,9 @@ GEM signet (>= 0.16, < 2.a) groupdate (6.2.1) activesupport (>= 5.2) - grpc (1.63.0) - google-protobuf (~> 3.25) - googleapis-common-protos-types (~> 1.0) grpc (1.63.0-arm64-darwin) google-protobuf (~> 3.25) googleapis-common-protos-types (~> 1.0) - grpc (1.63.0-x86_64-darwin) - google-protobuf (~> 3.25) - googleapis-common-protos-types (~> 1.0) - grpc (1.63.0-x86_64-linux) - google-protobuf (~> 3.25) - googleapis-common-protos-types (~> 1.0) haikunator (1.1.1) hairtrigger (1.0.0) activerecord (>= 6.0, < 8) @@ -355,7 +346,6 @@ GEM hana (1.3.7) hashdiff (1.0.1) hashie (5.0.0) - hkdf (1.0.0) http (5.1.1) addressable (~> 2.8) http-cookie (~> 1.0) @@ -416,15 +406,8 @@ GEM addressable (~> 2.8) letter_opener (1.8.1) launchy (>= 2.2, < 3) - libdatadog (2.0.0.1.0) - libdatadog (2.0.0.1.0-x86_64-linux) - libddwaf (1.8.2.0.0) - ffi (~> 1.0) - libddwaf (1.8.2.0.0-arm64-darwin) - ffi (~> 1.0) - libddwaf (1.8.2.0.0-x86_64-darwin) - ffi (~> 1.0) - libddwaf (1.8.2.0.0-x86_64-linux) + libdatadog (7.0.0.1.0) + libddwaf (1.14.0.0.0-arm64-darwin) ffi (~> 1.0) line-bot-api (1.28.0) liquid (5.4.0) @@ -434,6 +417,7 @@ GEM llhttp-ffi (0.4.0) ffi-compiler (~> 1.0) rake (~> 13.0) + logger (1.6.0) lograge (0.14.0) actionpack (>= 4) activesupport (>= 4) @@ -459,8 +443,7 @@ GEM mime-types-data (3.2023.0218.1) mini_magick (4.12.0) mini_mime (1.1.5) - mini_portile2 (2.8.5) - minitest (5.21.2) + minitest (5.24.1) mock_redis (0.36.0) ruby2_keywords msgpack (1.7.0) @@ -471,7 +454,7 @@ GEM activerecord (>= 5.2) net-http-persistent (4.0.2) connection_pool (~> 2.2) - net-imap (0.4.9) + net-imap (0.4.12) date net-protocol net-pop (0.1.2) @@ -486,15 +469,8 @@ GEM sidekiq newrelic_rpm (9.6.0) base64 - nio4r (2.7.0) - nokogiri (1.16.2) - mini_portile2 (~> 2.8.2) - racc (~> 1.4) - nokogiri (1.16.2-arm64-darwin) - racc (~> 1.4) - nokogiri (1.16.2-x86_64-darwin) - racc (~> 1.4) - nokogiri (1.16.2-x86_64-linux) + nio4r (2.7.3) + nokogiri (1.16.6-arm64-darwin) racc (~> 1.4) oauth (1.1.0) oauth-tty (~> 1.0, >= 1.0.1) @@ -524,7 +500,7 @@ GEM omniauth-rails_csrf_protection (1.0.2) actionpack (>= 4.2) omniauth (~> 2.0) - openssl (3.1.0) + openssl (3.2.0) orm_adapter (0.5.0) os (1.1.4) parallel (1.23.0) @@ -548,11 +524,11 @@ GEM pundit (2.3.0) activesupport (>= 3.0.0) raabro (1.4.0) - racc (1.7.3) - rack (2.2.8) + racc (1.8.0) + rack (2.2.9) rack-attack (6.7.0) rack (>= 1.0, < 4) - rack-contrib (2.4.0) + rack-contrib (2.5.0) rack (< 4) rack-cors (2.0.0) rack (>= 2.0.0) @@ -565,20 +541,20 @@ GEM rack-test (2.1.0) rack (>= 1.3) rack-timeout (0.6.3) - rails (7.0.8.3) - actioncable (= 7.0.8.3) - actionmailbox (= 7.0.8.3) - actionmailer (= 7.0.8.3) - actionpack (= 7.0.8.3) - actiontext (= 7.0.8.3) - actionview (= 7.0.8.3) - activejob (= 7.0.8.3) - activemodel (= 7.0.8.3) - activerecord (= 7.0.8.3) - activestorage (= 7.0.8.3) - activesupport (= 7.0.8.3) + rails (7.0.8.4) + actioncable (= 7.0.8.4) + actionmailbox (= 7.0.8.4) + actionmailer (= 7.0.8.4) + actionpack (= 7.0.8.4) + actiontext (= 7.0.8.4) + actionview (= 7.0.8.4) + activejob (= 7.0.8.4) + activemodel (= 7.0.8.4) + activerecord (= 7.0.8.4) + activestorage (= 7.0.8.4) + activesupport (= 7.0.8.4) bundler (>= 1.15.0) - railties (= 7.0.8.3) + railties (= 7.0.8.4) rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest @@ -586,9 +562,9 @@ GEM rails-html-sanitizer (1.6.0) loofah (~> 2.21) nokogiri (~> 1.14) - railties (7.0.8.3) - actionpack (= 7.0.8.3) - activesupport (= 7.0.8.3) + railties (7.0.8.4) + actionpack (= 7.0.8.4) + activesupport (= 7.0.8.4) method_source rake (>= 12.2) thor (~> 1.0) @@ -600,7 +576,7 @@ GEM ffi (~> 1.0) redis (5.0.6) redis-client (>= 0.9.0) - redis-client (0.19.1) + redis-client (0.22.2) connection_pool redis-namespace (1.10.0) redis (>= 4) @@ -624,24 +600,25 @@ GEM retriable (3.1.2) reverse_markdown (2.1.1) nokogiri - rexml (3.2.5) - rspec-core (3.12.2) - rspec-support (~> 3.12.0) - rspec-expectations (3.12.3) + rexml (3.3.1) + strscan + rspec-core (3.13.0) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.1) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.12.0) - rspec-mocks (3.12.5) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.1) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.12.0) - rspec-rails (6.0.2) + rspec-support (~> 3.13.0) + rspec-rails (6.1.3) actionpack (>= 6.1) activesupport (>= 6.1) railties (>= 6.1) - rspec-core (~> 3.12) - rspec-expectations (~> 3.12) - rspec-mocks (~> 3.12) - rspec-support (~> 3.12) - rspec-support (3.12.0) + rspec-core (~> 3.13) + rspec-expectations (~> 3.13) + rspec-mocks (~> 3.13) + rspec-support (~> 3.13) + rspec-support (3.13.1) rspec_junit_formatter (0.6.0) rspec-core (>= 2, < 4, != 2.12.0) rubocop (1.50.2) @@ -699,23 +676,24 @@ GEM activesupport (>= 4) selectize-rails (0.12.6) semantic_range (3.0.0) - sentry-rails (5.17.3) + sentry-rails (5.18.1) railties (>= 5.0) - sentry-ruby (~> 5.17.3) - sentry-ruby (5.17.3) + sentry-ruby (~> 5.18.1) + sentry-ruby (5.18.1) bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) - sentry-sidekiq (5.17.3) - sentry-ruby (~> 5.17.3) + sentry-sidekiq (5.18.1) + sentry-ruby (~> 5.18.1) sidekiq (>= 3.0) sexp_processor (4.17.0) shoulda-matchers (5.3.0) activesupport (>= 5.2.0) - sidekiq (7.2.4) + sidekiq (7.3.0) concurrent-ruby (< 2) connection_pool (>= 2.3.0) + logger rack (>= 2.2.4) - redis-client (>= 0.19.0) + redis-client (>= 0.22.2) sidekiq-cron (1.12.0) fugit (~> 1.8) globalid (>= 1.0.1) @@ -754,6 +732,7 @@ GEM stackprof (0.2.25) statsd-ruby (1.5.0) stripe (8.5.0) + strscan (3.1.0) telephone_number (1.4.20) test-prof (1.2.1) thor (1.3.0) @@ -793,8 +772,7 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - web-push (3.0.0) - hkdf (~> 1.0) + web-push (3.0.1) jwt (~> 2.0) openssl (~> 3.0) webmock (3.18.1) @@ -814,18 +792,10 @@ GEM working_hours (1.4.1) activesupport (>= 3.2) tzinfo - zeitwerk (2.6.12) + zeitwerk (2.6.16) PLATFORMS - arm64-darwin-20 - arm64-darwin-21 - arm64-darwin-22 - ruby - x86_64-darwin-18 - x86_64-darwin-20 - x86_64-darwin-21 - x86_64-darwin-22 - x86_64-linux + arm64-darwin-23 DEPENDENCIES active_record_query_trace @@ -856,7 +826,7 @@ DEPENDENCIES devise (>= 4.9.4) devise-secure_password! devise_token_auth (>= 1.2.3) - dotenv-rails + dotenv-rails (>= 3.0.0) down elastic-apm email_reply_trimmer @@ -911,13 +881,13 @@ DEPENDENCIES rack-cors (= 2.0.0) rack-mini-profiler (>= 3.2.0) rack-timeout - rails (~> 7.0.8.1) + rails (~> 7.0.8.4) redis redis-namespace responders (>= 3.1.1) rest-client reverse_markdown - rspec-rails + rspec-rails (>= 6.1.3) rspec_junit_formatter rubocop rubocop-performance @@ -926,11 +896,11 @@ DEPENDENCIES scout_apm scss_lint seed_dump - sentry-rails (>= 5.14.0) + sentry-rails (>= 5.18.1) sentry-ruby - sentry-sidekiq (>= 5.15.0) + sentry-sidekiq (>= 5.18.1) shoulda-matchers - sidekiq (>= 7.2.4) + sidekiq (>= 7.3.0) sidekiq-cron (>= 1.12.0) simplecov (= 0.17.1) slack-ruby-client (~> 2.2.0) @@ -948,14 +918,14 @@ DEPENDENCIES uglifier valid_email2 web-console (>= 4.2.1) - web-push + web-push (>= 3.0.1) webmock webpacker wisper (= 2.0.0) working_hours RUBY VERSION - ruby 3.2.2p185 + ruby 3.3.3p89 BUNDLED WITH - 2.4.6 + 2.5.14 diff --git a/VERSION_CW b/VERSION_CW index bea438e9a..a5c4c7633 100644 --- a/VERSION_CW +++ b/VERSION_CW @@ -1 +1 @@ -3.3.1 +3.9.0 diff --git a/VERSION_CWCTL b/VERSION_CWCTL index 24ba9a38d..834f26295 100644 --- a/VERSION_CWCTL +++ b/VERSION_CWCTL @@ -1 +1 @@ -2.7.0 +2.8.0 diff --git a/app/controllers/api/v1/accounts/google/authorizations_controller.rb b/app/controllers/api/v1/accounts/google/authorizations_controller.rb new file mode 100644 index 000000000..1140a214b --- /dev/null +++ b/app/controllers/api/v1/accounts/google/authorizations_controller.rb @@ -0,0 +1,32 @@ +class Api::V1::Accounts::Google::AuthorizationsController < Api::V1::Accounts::BaseController + include GoogleConcern + before_action :check_authorization + + def create + email = params[:authorization][:email] + redirect_url = google_client.auth_code.authorize_url( + { + redirect_uri: "#{base_url}/google/callback", + scope: 'email profile https://mail.google.com/', + response_type: 'code', + prompt: 'consent', # the oauth flow does not return a refresh token, this is supposed to fix it + access_type: 'offline', # the default is 'online' + client_id: GlobalConfigService.load('GOOGLE_OAUTH_CLIENT_ID', nil) + } + ) + + if redirect_url + cache_key = "google::#{email.downcase}" + ::Redis::Alfred.setex(cache_key, Current.account.id, 5.minutes) + render json: { success: true, url: redirect_url } + else + render json: { success: false }, status: :unprocessable_entity + end + end + + private + + def check_authorization + raise Pundit::NotAuthorizedError unless Current.account_user.administrator? + end +end diff --git a/app/controllers/api/v1/accounts/integrations/linear_controller.rb b/app/controllers/api/v1/accounts/integrations/linear_controller.rb index 9d5d76d75..814373c7e 100644 --- a/app/controllers/api/v1/accounts/integrations/linear_controller.rb +++ b/app/controllers/api/v1/accounts/integrations/linear_controller.rb @@ -88,6 +88,6 @@ class Api::V1::Accounts::Integrations::LinearController < Api::V1::Accounts::Bas end def permitted_params - params.permit(:team_id, :conversation_id, :issue_id, :link_id, :title, :description, :assignee_id, :priority, label_ids: []) + params.permit(:team_id, :project_id, :conversation_id, :issue_id, :link_id, :title, :description, :assignee_id, :priority, label_ids: []) end end diff --git a/app/controllers/api/v1/accounts/microsoft/authorizations_controller.rb b/app/controllers/api/v1/accounts/microsoft/authorizations_controller.rb index bee47b213..df563094a 100644 --- a/app/controllers/api/v1/accounts/microsoft/authorizations_controller.rb +++ b/app/controllers/api/v1/accounts/microsoft/authorizations_controller.rb @@ -12,8 +12,8 @@ class Api::V1::Accounts::Microsoft::AuthorizationsController < Api::V1::Accounts } ) if redirect_url - email = email.downcase - ::Redis::Alfred.setex(email, Current.account.id, 5.minutes) + cache_key = "microsoft::#{email.downcase}" + ::Redis::Alfred.setex(cache_key, Current.account.id, 5.minutes) render json: { success: true, url: redirect_url } else render json: { success: false }, status: :unprocessable_entity diff --git a/app/controllers/concerns/google_concern.rb b/app/controllers/concerns/google_concern.rb new file mode 100644 index 000000000..474b14aec --- /dev/null +++ b/app/controllers/concerns/google_concern.rb @@ -0,0 +1,20 @@ +module GoogleConcern + extend ActiveSupport::Concern + + def google_client + app_id = GlobalConfigService.load('GOOGLE_OAUTH_CLIENT_ID', nil) + app_secret = GlobalConfigService.load('GOOGLE_OAUTH_CLIENT_SECRET', nil) + + ::OAuth2::Client.new(app_id, app_secret, { + site: 'https://oauth2.googleapis.com', + authorize_url: 'https://accounts.google.com/o/oauth2/auth', + token_url: 'https://accounts.google.com/o/oauth2/token' + }) + end + + private + + def base_url + ENV.fetch('FRONTEND_URL', 'http://localhost:3000') + end +end diff --git a/app/controllers/concerns/microsoft_concern.rb b/app/controllers/concerns/microsoft_concern.rb index 0f37bd03f..507b9f8a3 100644 --- a/app/controllers/concerns/microsoft_concern.rb +++ b/app/controllers/concerns/microsoft_concern.rb @@ -15,10 +15,6 @@ module MicrosoftConcern private - def parsed_body - @parsed_body ||= Rack::Utils.parse_nested_query(@response.raw_response.body) - end - def base_url ENV.fetch('FRONTEND_URL', 'http://localhost:3000') end diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 521d027cc..332f1528f 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -3,6 +3,7 @@ class DashboardController < ActionController::Base before_action :set_application_pack before_action :set_global_config + before_action :set_dashboard_scripts around_action :switch_locale before_action :ensure_installation_onboarding, only: [:index] before_action :render_hc_if_custom_domain, only: [:index] @@ -35,6 +36,10 @@ class DashboardController < ActionController::Base ).merge(app_config) end + def set_dashboard_scripts + @dashboard_scripts = sensitive_path? ? nil : GlobalConfig.get_value('DASHBOARD_SCRIPTS') + end + def ensure_installation_onboarding redirect_to '/installation/onboarding' if ::Redis::Alfred.get(::Redis::Alfred::CHATWOOT_INSTALLATION_ONBOARDING) end @@ -70,4 +75,14 @@ class DashboardController < ActionController::Base 'application' end end + + def sensitive_path? + # dont load dashboard scripts on sensitive paths like password reset + sensitive_paths = [edit_user_password_path].freeze + + # remove app prefix + current_path = request.path.gsub(%r{^/app}, '') + + sensitive_paths.include?(current_path) + end end diff --git a/app/controllers/google/callbacks_controller.rb b/app/controllers/google/callbacks_controller.rb new file mode 100644 index 000000000..391e1de0f --- /dev/null +++ b/app/controllers/google/callbacks_controller.rb @@ -0,0 +1,18 @@ +class Google::CallbacksController < OauthCallbackController + include GoogleConcern + + private + + def provider_name + 'google' + end + + def imap_address + 'imap.gmail.com' + end + + def oauth_client + # from GoogleConcern + google_client + end +end diff --git a/app/controllers/microsoft/callbacks_controller.rb b/app/controllers/microsoft/callbacks_controller.rb index 215103bd4..2f07505fc 100644 --- a/app/controllers/microsoft/callbacks_controller.rb +++ b/app/controllers/microsoft/callbacks_controller.rb @@ -1,77 +1,17 @@ -class Microsoft::CallbacksController < ApplicationController +class Microsoft::CallbacksController < OauthCallbackController include MicrosoftConcern - def show - @response = microsoft_client.auth_code.get_token( - oauth_code, - redirect_uri: "#{base_url}/microsoft/callback" - ) - - inbox = find_or_create_inbox - ::Redis::Alfred.delete(users_data['email'].downcase) - redirect_to app_microsoft_inbox_agents_url(account_id: account.id, inbox_id: inbox.id) - rescue StandardError => e - ChatwootExceptionTracker.new(e).capture_exception - redirect_to '/' - end - private - def oauth_code - params[:code] + def oauth_client + microsoft_client end - def users_data - decoded_token = JWT.decode parsed_body[:id_token], nil, false - decoded_token[0] + def provider_name + 'microsoft' end - def parsed_body - @parsed_body ||= @response.response.parsed - end - - def account_id - ::Redis::Alfred.get(users_data['email'].downcase) - end - - def account - @account ||= Account.find(account_id) - end - - def find_or_create_inbox - channel_email = Channel::Email.find_by(email: users_data['email'], account: account) - channel_email ||= create_microsoft_channel_with_inbox - update_microsoft_channel(channel_email) - channel_email.inbox - end - - # Fallback name, for when name field is missing from users_data - def fallback_name - users_data['email'].split('@').first.parameterize.titleize - end - - def create_microsoft_channel_with_inbox - ActiveRecord::Base.transaction do - channel_email = Channel::Email.create!(email: users_data['email'], account: account) - account.inboxes.create!( - account: account, - channel: channel_email, - name: users_data['name'] || fallback_name - ) - channel_email - end - end - - def update_microsoft_channel(channel_email) - channel_email.update!({ - imap_login: users_data['email'], imap_address: 'outlook.office365.com', - imap_port: '993', imap_enabled: true, - provider: 'microsoft', - provider_config: { - access_token: parsed_body['access_token'], - refresh_token: parsed_body['refresh_token'], - expires_on: (Time.current.utc + 1.hour).to_s - } - }) + def imap_address + 'outlook.office365.com' end end diff --git a/app/controllers/oauth_callback_controller.rb b/app/controllers/oauth_callback_controller.rb new file mode 100644 index 000000000..309160f1f --- /dev/null +++ b/app/controllers/oauth_callback_controller.rb @@ -0,0 +1,108 @@ +class OauthCallbackController < ApplicationController + def show + @response = oauth_client.auth_code.get_token( + oauth_code, + redirect_uri: "#{base_url}/#{provider_name}/callback" + ) + + handle_response + ::Redis::Alfred.delete(cache_key) + rescue StandardError => e + ChatwootExceptionTracker.new(e).capture_exception + redirect_to '/' + end + + private + + def handle_response + inbox, already_exists = find_or_create_inbox + + if already_exists + redirect_to app_email_inbox_settings_url(account_id: account.id, inbox_id: inbox.id) + else + redirect_to app_email_inbox_agents_url(account_id: account.id, inbox_id: inbox.id) + end + end + + def find_or_create_inbox + channel_email = Channel::Email.find_by(email: users_data['email'], account: account) + # we need this value to know where to redirect on sucessful processing of the callback + channel_exists = channel_email.present? + + channel_email ||= create_channel_with_inbox + update_channel(channel_email) + + # reauthorize channel, this code path only triggers when microsoft auth is successful + # reauthorized will also update cache keys for the associated inbox + channel_email.reauthorized! + + [channel_email.inbox, channel_exists] + end + + def update_channel(channel_email) + channel_email.update!({ + imap_login: users_data['email'], imap_address: imap_address, + imap_port: '993', imap_enabled: true, + provider: provider_name, + provider_config: { + access_token: parsed_body['access_token'], + refresh_token: parsed_body['refresh_token'], + expires_on: (Time.current.utc + 1.hour).to_s + } + }) + end + + def provider_name + raise NotImplementedError + end + + def oauth_client + raise NotImplementedError + end + + def cache_key + "#{provider_name}::#{users_data['email'].downcase}" + end + + def create_channel_with_inbox + ActiveRecord::Base.transaction do + channel_email = Channel::Email.create!(email: users_data['email'], account: account) + account.inboxes.create!( + account: account, + channel: channel_email, + name: users_data['name'] || fallback_name + ) + channel_email + end + end + + def users_data + decoded_token = JWT.decode parsed_body[:id_token], nil, false + decoded_token[0] + end + + def account_id + ::Redis::Alfred.get(cache_key) + end + + def account + @account ||= Account.find(account_id) + end + + # Fallback name, for when name field is missing from users_data + def fallback_name + users_data['email'].split('@').first.parameterize.titleize + end + + def oauth_code + params[:code] + end + + def base_url + ENV.fetch('FRONTEND_URL', 'http://localhost:3000') + end + + def parsed_body + @parsed_body ||= @response.response.parsed + end +end diff --git a/app/controllers/super_admin/app_configs_controller.rb b/app/controllers/super_admin/app_configs_controller.rb index 9d9494fc4..b8f3bd9a9 100644 --- a/app/controllers/super_admin/app_configs_controller.rb +++ b/app/controllers/super_admin/app_configs_controller.rb @@ -40,7 +40,7 @@ class SuperAdmin::AppConfigsController < SuperAdmin::ApplicationController when 'email' ['MAILER_INBOUND_EMAIL_DOMAIN'] else - %w[ENABLE_ACCOUNT_SIGNUP] + %w[ENABLE_ACCOUNT_SIGNUP FIREBASE_PROJECT_ID FIREBASE_CREDENTIALS] end end end diff --git a/app/javascript/dashboard/App.vue b/app/javascript/dashboard/App.vue index 481c671ce..c343fbe4f 100644 --- a/app/javascript/dashboard/App.vue +++ b/app/javascript/dashboard/App.vue @@ -27,6 +27,7 @@ diff --git a/app/javascript/dashboard/components/Code.vue b/app/javascript/dashboard/components/Code.vue index 269311c2d..d8f609e29 100644 --- a/app/javascript/dashboard/components/Code.vue +++ b/app/javascript/dashboard/components/Code.vue @@ -25,8 +25,10 @@ + - - diff --git a/app/javascript/dashboard/components/SidemenuIcon.vue b/app/javascript/dashboard/components/SidemenuIcon.vue index e2811f65b..6106b9ad0 100644 --- a/app/javascript/dashboard/components/SidemenuIcon.vue +++ b/app/javascript/dashboard/components/SidemenuIcon.vue @@ -21,7 +21,7 @@ export default { }, methods: { onMenuItemClick() { - bus.$emit(BUS_EVENTS.TOGGLE_SIDEMENU); + this.$emitter.emit(BUS_EVENTS.TOGGLE_SIDEMENU); }, }, }; diff --git a/app/javascript/dashboard/components/SnackbarContainer.vue b/app/javascript/dashboard/components/SnackbarContainer.vue index 553ad2a2e..4785e4d4b 100644 --- a/app/javascript/dashboard/components/SnackbarContainer.vue +++ b/app/javascript/dashboard/components/SnackbarContainer.vue @@ -15,11 +15,13 @@ + + diff --git a/app/javascript/dashboard/components/ui/DatePicker/DatePicker.vue b/app/javascript/dashboard/components/ui/DatePicker/DatePicker.vue index 225bb90f3..9b84d6548 100644 --- a/app/javascript/dashboard/components/ui/DatePicker/DatePicker.vue +++ b/app/javascript/dashboard/components/ui/DatePicker/DatePicker.vue @@ -22,7 +22,7 @@ import { setYear, isAfter, } from 'date-fns'; - +import { useAlert } from 'dashboard/composables'; import DatePickerButton from './components/DatePickerButton.vue'; import CalendarDateInput from './components/CalendarDateInput.vue'; import CalendarDateRange from './components/CalendarDateRange.vue'; @@ -185,7 +185,7 @@ const updateManualInput = (newDate, calendarType) => { }; const handleManualInputError = message => { - bus.$emit('newToastMessage', message); + useAlert(message); }; const resetDatePicker = () => { @@ -201,7 +201,7 @@ const resetDatePicker = () => { const emitDateRange = () => { if (!isValid(selectedStartDate.value) || !isValid(selectedEndDate.value)) { - bus.$emit('newToastMessage', 'Please select a valid time range'); + useAlert('Please select a valid time range'); } else { showDatePicker.value = false; emit('dateRangeChanged', [selectedStartDate.value, selectedEndDate.value]); diff --git a/app/javascript/dashboard/components/ui/Dropdown/DropdownList.vue b/app/javascript/dashboard/components/ui/Dropdown/DropdownList.vue index 88efa5f39..0c6942e1b 100644 --- a/app/javascript/dashboard/components/ui/Dropdown/DropdownList.vue +++ b/app/javascript/dashboard/components/ui/Dropdown/DropdownList.vue @@ -1,9 +1,11 @@