Feature: Access tokens for API access (#604)
Co-authored-by: Pranav Raj Sreepuram <pranavrajs@gmail.com>
This commit is contained in:
@@ -22,7 +22,7 @@ class Messages::Outgoing::NormalBuilder
|
||||
message_type: :outgoing,
|
||||
content: @content,
|
||||
private: @private,
|
||||
user_id: @user.id,
|
||||
user_id: @user&.id,
|
||||
source_id: @fb_id
|
||||
}
|
||||
end
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
class Api::BaseController < ApplicationController
|
||||
include AccessTokenAuthHelper
|
||||
respond_to :json
|
||||
before_action :authenticate_user!
|
||||
before_action :authenticate_access_token!, if: :authenticate_by_access_token?
|
||||
before_action :validate_bot_access_token!, if: :authenticate_by_access_token?
|
||||
before_action :authenticate_user!, unless: :authenticate_by_access_token?
|
||||
|
||||
private
|
||||
|
||||
def authenticate_by_access_token?
|
||||
request.headers[:api_access_token].present?
|
||||
end
|
||||
|
||||
def set_conversation
|
||||
@conversation ||= current_account.conversations.find_by(display_id: params[:conversation_id])
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class Api::V1::Widget::InboxesController < Api::BaseController
|
||||
class Api::V1::Accounts::Widget::InboxesController < Api::BaseController
|
||||
before_action :authorize_request
|
||||
before_action :set_web_widget_channel, only: [:update]
|
||||
before_action :set_inbox, only: [:update]
|
||||
@@ -14,7 +14,25 @@ class ApplicationController < ActionController::Base
|
||||
private
|
||||
|
||||
def current_account
|
||||
@_ ||= current_user.account
|
||||
@_ ||= find_current_account
|
||||
end
|
||||
|
||||
def find_current_account
|
||||
account = Account.find(params[:account_id])
|
||||
if current_user
|
||||
account_accessible_for_user?(account)
|
||||
elsif @resource&.is_a?(AgentBot)
|
||||
account_accessible_for_bot?(account)
|
||||
end
|
||||
account
|
||||
end
|
||||
|
||||
def account_accessible_for_user?(account)
|
||||
render_unauthorized('You are not authorized to access this account') unless account.account_users.find_by(user_id: current_user.id)
|
||||
end
|
||||
|
||||
def account_accessible_for_bot?(account)
|
||||
render_unauthorized('You are not authorized to access this account') unless @resource.agent_bot_inboxes.find_by(account_id: account.id)
|
||||
end
|
||||
|
||||
def handle_with_exception
|
||||
|
||||
24
app/controllers/concerns/access_token_auth_helper.rb
Normal file
24
app/controllers/concerns/access_token_auth_helper.rb
Normal file
@@ -0,0 +1,24 @@
|
||||
module AccessTokenAuthHelper
|
||||
BOT_ACCESSIBLE_ENDPOINTS = {
|
||||
'api/v1/accounts/conversations' => ['toggle_status'],
|
||||
'api/v1/accounts/conversations/messages' => ['create']
|
||||
}.freeze
|
||||
|
||||
def authenticate_access_token!
|
||||
access_token = AccessToken.find_by(token: request.headers[:api_access_token])
|
||||
render_unauthorized('Invalid Access Token') && return unless access_token
|
||||
token_owner = access_token.owner
|
||||
@resource = token_owner
|
||||
end
|
||||
|
||||
def validate_bot_access_token!
|
||||
return if current_user.is_a?(User)
|
||||
return if agent_bot_accessible?
|
||||
|
||||
render_unauthorized('Access to this endpoint is not authorized for bots')
|
||||
end
|
||||
|
||||
def agent_bot_accessible?
|
||||
BOT_ACCESSIBLE_ENDPOINTS.fetch(params[:controller], []).include?(params[:action])
|
||||
end
|
||||
end
|
||||
@@ -2,7 +2,7 @@ import ApiClient from '../ApiClient';
|
||||
|
||||
class WebChannel extends ApiClient {
|
||||
constructor() {
|
||||
super('widget/inboxes');
|
||||
super('widget/inboxes', { accountScoped: true });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,8 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onCopy() {
|
||||
onCopy(e) {
|
||||
e.preventDefault();
|
||||
copy(this.script);
|
||||
bus.$emit('newToastMessage', this.$t('COMPONENTS.CODE.COPY_SUCCESSFUL'));
|
||||
},
|
||||
|
||||
@@ -18,6 +18,10 @@
|
||||
"TITLE": "Password",
|
||||
"NOTE": "Updating your password would reset your logins in multiple devices."
|
||||
},
|
||||
"ACCESS_TOKEN": {
|
||||
"TITLE": "Access Token",
|
||||
"NOTE": "This token can be used if you are building an API based integration"
|
||||
},
|
||||
"EMAIL_NOTIFICATIONS_SECTION" : {
|
||||
"TITLE": "Email Notifications",
|
||||
"NOTE": "Update your email notification preferences here",
|
||||
|
||||
@@ -83,6 +83,17 @@
|
||||
</div>
|
||||
</div>
|
||||
<email-notifications />
|
||||
<div class="profile--settings--row row">
|
||||
<div class="columns small-3 ">
|
||||
<h4 class="block-title">
|
||||
{{ $t('PROFILE_SETTINGS.FORM.ACCESS_TOKEN.TITLE') }}
|
||||
</h4>
|
||||
<p>{{ $t('PROFILE_SETTINGS.FORM.ACCESS_TOKEN.NOTE') }}</p>
|
||||
</div>
|
||||
<div class="columns small-9 medium-5">
|
||||
<woot-code :script="currentUser.access_token"></woot-code>
|
||||
</div>
|
||||
</div>
|
||||
<woot-submit-button
|
||||
class="button nice success button--fixed-right-top"
|
||||
:button-text="$t('PROFILE_SETTINGS.BTN_TEXT')"
|
||||
|
||||
21
app/models/access_token.rb
Normal file
21
app/models/access_token.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: access_tokens
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# owner_type :string
|
||||
# token :string
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# owner_id :bigint
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_access_tokens_on_owner_type_and_owner_id (owner_type,owner_id)
|
||||
# index_access_tokens_on_token (token) UNIQUE
|
||||
#
|
||||
|
||||
class AccessToken < ApplicationRecord
|
||||
has_secure_token :token
|
||||
belongs_to :owner, polymorphic: true
|
||||
end
|
||||
@@ -14,6 +14,7 @@ class Account < ApplicationRecord
|
||||
validates :name, presence: true
|
||||
|
||||
has_many :account_users, dependent: :destroy
|
||||
has_many :agent_bot_inboxes, dependent: :destroy
|
||||
has_many :users, through: :account_users
|
||||
has_many :inboxes, dependent: :destroy
|
||||
has_many :conversations, dependent: :destroy
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
# Table name: agent_bots
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# auth_token :string
|
||||
# description :string
|
||||
# name :string
|
||||
# outgoing_url :string
|
||||
@@ -12,8 +11,9 @@
|
||||
#
|
||||
|
||||
class AgentBot < ApplicationRecord
|
||||
include AccessTokenable
|
||||
include Avatarable
|
||||
|
||||
has_many :agent_bot_inboxes, dependent: :destroy
|
||||
has_many :inboxes, through: :agent_bot_inboxes
|
||||
has_secure_token :auth_token
|
||||
end
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
# status :integer default("active")
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# account_id :integer
|
||||
# agent_bot_id :integer
|
||||
# inbox_id :integer
|
||||
#
|
||||
@@ -13,8 +14,16 @@
|
||||
class AgentBotInbox < ApplicationRecord
|
||||
validates :inbox_id, presence: true
|
||||
validates :agent_bot_id, presence: true
|
||||
before_validation :ensure_account_id
|
||||
|
||||
belongs_to :inbox
|
||||
belongs_to :agent_bot
|
||||
belongs_to :account
|
||||
enum status: { active: 0, inactive: 1 }
|
||||
|
||||
private
|
||||
|
||||
def ensure_account_id
|
||||
self.account_id = inbox&.account_id
|
||||
end
|
||||
end
|
||||
|
||||
11
app/models/concerns/access_tokenable.rb
Normal file
11
app/models/concerns/access_tokenable.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
module AccessTokenable
|
||||
extend ActiveSupport::Concern
|
||||
included do
|
||||
has_one :access_token, as: :owner, dependent: :destroy
|
||||
after_create :create_access_token
|
||||
end
|
||||
|
||||
def create_access_token
|
||||
AccessToken.create!(owner: self)
|
||||
end
|
||||
end
|
||||
@@ -67,7 +67,9 @@ class Conversation < ApplicationRecord
|
||||
end
|
||||
|
||||
def toggle_status
|
||||
# FIXME: implement state machine with aasm
|
||||
self.status = open? ? :resolved : :open
|
||||
self.status = :open if bot?
|
||||
save
|
||||
end
|
||||
|
||||
|
||||
@@ -35,12 +35,13 @@
|
||||
#
|
||||
|
||||
class User < ApplicationRecord
|
||||
include AccessTokenable
|
||||
include AvailabilityStatusable
|
||||
include Avatarable
|
||||
# Include default devise modules.
|
||||
include DeviseTokenAuth::Concerns::User
|
||||
include Events::Types
|
||||
include Pubsubable
|
||||
include Avatarable
|
||||
include AvailabilityStatusable
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
devise :database_authenticatable,
|
||||
@@ -69,7 +70,7 @@ class User < ApplicationRecord
|
||||
|
||||
before_validation :set_password_and_uid, on: :create
|
||||
|
||||
after_create :notify_creation
|
||||
after_create :notify_creation, :create_access_token
|
||||
|
||||
after_destroy :notify_deletion
|
||||
|
||||
|
||||
@@ -13,5 +13,6 @@ json.payload do
|
||||
json.inviter_id @resource.account_user.inviter_id
|
||||
json.confirmed @resource.confirmed?
|
||||
json.avatar_url @resource.avatar_url
|
||||
json.access_token @resource.access_token&.token
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user