feat: autogenerate vapid keys for push notifications (#3128)
* feat: Autogenerate push notification keys * add vapid service class and remove pushkey model * add spec for vapid service * unset vapid env keys * Unset VAPID_PRIVATE_KEY env variable Co-authored-by: Sojan Jose <sojan@chatwoot.com> Co-authored-by: Vishnu Narayanan <vishnu@chatwoot.com>
This commit is contained in:
parent
0c9b682329
commit
6cfd7d3836
7 changed files with 86 additions and 8 deletions
|
@ -28,6 +28,7 @@ class DashboardController < ActionController::Base
|
||||||
'ANALYTICS_HOST'
|
'ANALYTICS_HOST'
|
||||||
).merge(
|
).merge(
|
||||||
APP_VERSION: Chatwoot.config[:version],
|
APP_VERSION: Chatwoot.config[:version],
|
||||||
|
VAPID_PUBLIC_KEY: VapidService.public_key,
|
||||||
ENABLE_ACCOUNT_SIGNUP: GlobalConfigService.load('ENABLE_ACCOUNT_SIGNUP', 'false')
|
ENABLE_ACCOUNT_SIGNUP: GlobalConfigService.load('ENABLE_ACCOUNT_SIGNUP', 'false')
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
|
@ -34,5 +34,4 @@ describe('campaignMixin', () => {
|
||||||
const wrapper = shallowMount(Component);
|
const wrapper = shallowMount(Component);
|
||||||
expect(wrapper.vm.isOnOffType).toBe(true);
|
expect(wrapper.vm.isOnOffType).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,7 @@ import events from 'widget/api/events';
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
}
|
};
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
create: async (_, { name }) => {
|
create: async (_, { name }) => {
|
||||||
|
@ -17,7 +17,7 @@ const actions = {
|
||||||
const mutations = {
|
const mutations = {
|
||||||
toggleOpen($state) {
|
toggleOpen($state) {
|
||||||
$state.isOpen = !$state.isOpen;
|
$state.isOpen = !$state.isOpen;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -43,7 +43,7 @@ class Notification::PushNotificationService
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_browser_push?(subscription)
|
def send_browser_push?(subscription)
|
||||||
ENV['VAPID_PUBLIC_KEY'] && subscription.browser_push?
|
VapidService.public_key && subscription.browser_push?
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_browser_push(subscription)
|
def send_browser_push(subscription)
|
||||||
|
@ -56,8 +56,8 @@ class Notification::PushNotificationService
|
||||||
auth: subscription.subscription_attributes['auth'],
|
auth: subscription.subscription_attributes['auth'],
|
||||||
vapid: {
|
vapid: {
|
||||||
subject: push_url,
|
subject: push_url,
|
||||||
public_key: ENV['VAPID_PUBLIC_KEY'],
|
public_key: VapidService.public_key,
|
||||||
private_key: ENV['VAPID_PRIVATE_KEY']
|
private_key: VapidService.private_key
|
||||||
},
|
},
|
||||||
ssl_timeout: 5,
|
ssl_timeout: 5,
|
||||||
open_timeout: 5,
|
open_timeout: 5,
|
||||||
|
|
|
@ -35,8 +35,8 @@
|
||||||
hostURL: '<%= ENV.fetch('FRONTEND_URL', '') %>',
|
hostURL: '<%= ENV.fetch('FRONTEND_URL', '') %>',
|
||||||
fbAppId: '<%= ENV.fetch('FB_APP_ID', nil) %>',
|
fbAppId: '<%= ENV.fetch('FB_APP_ID', nil) %>',
|
||||||
signupEnabled: '<%= @global_config['ENABLE_ACCOUNT_SIGNUP'] %>',
|
signupEnabled: '<%= @global_config['ENABLE_ACCOUNT_SIGNUP'] %>',
|
||||||
<% if ENV['VAPID_PUBLIC_KEY'] %>
|
<% if @global_config['VAPID_PUBLIC_KEY'] %>
|
||||||
vapidPublicKey: new Uint8Array(<%= Base64.urlsafe_decode64(ENV['VAPID_PUBLIC_KEY']).bytes %>),
|
vapidPublicKey: new Uint8Array(<%= Base64.urlsafe_decode64(@global_config['VAPID_PUBLIC_KEY']).bytes %>),
|
||||||
<% end %>
|
<% end %>
|
||||||
enabledLanguages: <%= available_locales_with_name.to_json.html_safe %>,
|
enabledLanguages: <%= available_locales_with_name.to_json.html_safe %>,
|
||||||
selectedLocale: '<%= I18n.locale %>'
|
selectedLocale: '<%= I18n.locale %>'
|
||||||
|
|
25
lib/vapid_service.rb
Normal file
25
lib/vapid_service.rb
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
class VapidService
|
||||||
|
def self.public_key
|
||||||
|
vapid_keys['public_key']
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.private_key
|
||||||
|
vapid_keys['private_key']
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.vapid_keys
|
||||||
|
config = GlobalConfig.get('VAPID_KEYS')
|
||||||
|
return config['VAPID_KEYS'] if config['VAPID_KEYS'].present?
|
||||||
|
|
||||||
|
# keys don't exist in the database. so let's generate and save them
|
||||||
|
keys = Webpush.generate_key
|
||||||
|
# TODO: remove the logic on environment variables when we completely deprecate
|
||||||
|
public_key = ENV['VAPID_PUBLIC_KEY'] || keys.public_key
|
||||||
|
private_key = ENV['VAPID_PRIVATE_KEY'] || keys.private_key
|
||||||
|
|
||||||
|
i = InstallationConfig.where(name: 'VAPID_KEYS').first_or_create(value: { public_key: public_key, private_key: private_key })
|
||||||
|
i.value
|
||||||
|
end
|
||||||
|
|
||||||
|
private_class_method :vapid_keys
|
||||||
|
end
|
53
spec/lib/vapid_service_spec.rb
Normal file
53
spec/lib/vapid_service_spec.rb
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe VapidService do
|
||||||
|
subject(:trigger) { described_class }
|
||||||
|
|
||||||
|
describe 'execute' do
|
||||||
|
context 'when called with default options' do
|
||||||
|
before do
|
||||||
|
GlobalConfig.clear_cache
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'hit DB for the first call' do
|
||||||
|
expect(InstallationConfig).to receive(:find_by)
|
||||||
|
GlobalConfig.get('VAPID_KEYS')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'get public key from env' do
|
||||||
|
# this gets public key
|
||||||
|
ENV['VAPID_PUBLIC_KEY'] = 'test'
|
||||||
|
described_class.public_key
|
||||||
|
|
||||||
|
# subsequent calls should not hit DB
|
||||||
|
expect(InstallationConfig).not_to receive(:find_by)
|
||||||
|
described_class.public_key
|
||||||
|
ENV['VAPID_PUBLIC_KEY'] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'get private key from env' do
|
||||||
|
# this gets private key
|
||||||
|
ENV['VAPID_PRIVATE_KEY'] = 'test'
|
||||||
|
described_class.private_key
|
||||||
|
|
||||||
|
# subsequent calls should not hit DB
|
||||||
|
expect(InstallationConfig).not_to receive(:find_by)
|
||||||
|
described_class.private_key
|
||||||
|
ENV['VAPID_PRIVATE_KEY'] = nil
|
||||||
|
ENV['VAPID_PRIVATE_KEY'] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'clears cache and fetch from DB next time, when clear_cache is called' do
|
||||||
|
# this loads from DB and is cached
|
||||||
|
GlobalConfig.get('VAPID_KEYS')
|
||||||
|
|
||||||
|
# clears the cache
|
||||||
|
GlobalConfig.clear_cache
|
||||||
|
|
||||||
|
# should be loaded from DB
|
||||||
|
expect(InstallationConfig).to receive(:find_by).with({ name: 'VAPID_KEYS' }).and_return(nil)
|
||||||
|
GlobalConfig.get('VAPID_KEYS')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue