Feature: Access tokens for API access (#604)

Co-authored-by: Pranav Raj Sreepuram <pranavrajs@gmail.com>
This commit is contained in:
Sojan Jose
2020-03-11 00:02:15 +05:30
committed by GitHub
parent 19ab0fe108
commit a5b1e2b650
29 changed files with 517 additions and 270 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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]

View File

@@ -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

View 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

View File

@@ -2,7 +2,7 @@ import ApiClient from '../ApiClient';
class WebChannel extends ApiClient {
constructor() {
super('widget/inboxes');
super('widget/inboxes', { accountScoped: true });
}
}

View File

@@ -26,7 +26,8 @@ export default {
},
},
methods: {
onCopy() {
onCopy(e) {
e.preventDefault();
copy(this.script);
bus.$emit('newToastMessage', this.$t('COMPONENTS.CODE.COPY_SUCCESSFUL'));
},

View File

@@ -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",

View File

@@ -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')"

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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