رؤية المشكلة بعيون عملائكم عبر انتحال هوية العميل (Customer Impersonation)
أي موقع يقدّم دعمًا للعملاء سيواجه في النهاية مشكلة يصعب على العميل شرحها، أو يصعب فهمها دون رؤية ما يراه. ومع pretender gem يمكننا تسجيل الدخول إلى حساب المستخدم ورؤية ما يراه بالضبط.

طلب الدعم لحل مشكلة تواجهونها كعملاء تجربة مرهقة وبطيئة في الغالب، ويزداد الأمر سوءًا حين يكون البريد الإلكتروني الوسيلة الوحيدة للتواصل مع الدعم. ويمكننا تحسين هذه التجربة بتقليل الجهد الذي يبذله العميل لشرح مشكلته بالكامل. فحين لا يستطيع موظفو الدعم رؤية المشكلة التي يواجهها المستخدم، عليهم جمع كمّ كبير من المعلومات واستيضاح كل تفصيل صغير للتأكد من أنهم فهموا العميل بشكل صحيح. ويمكننا تقليل المعلومات المطلوبة بمنح موظفي الدعم القدرة على تسجيل الدخول إلى حساب العميل. في هذا المقال سأستخدم pretender لأتيح لموظفي الدعم تسجيل الدخول باسم مستخدمين آخرين. يعمل Pretender مع أي نظام مصادقة، وسأستخدم هنا devise
الدخول في وضع انتحال الهوية
أولًا لنتأكد من تعريف جميع الـ gems في ملف Gemfile:
gem 'devise'
gem 'pundit'
gem 'pretender'
ونحتاج أيضًا إلى إضافة مقتطف إلى كل controller نريد أن يتمكن موظفو دعم العملاء من استخدامه أثناء وضع انتحال الهوية. لنضفه إلى ApplicationController حتى نتمكن من انتحال الهوية في كل أجزاء التطبيق:
class ApplicationController < ActionController::Base
before_action :authenticate_user!
impersonates :user
end
نحتاج إلى controller يتيح لموظفي الدعم الدخول في وضع انتحال هوية العميل والخروج منه:
class CustomerImpersonationsController < ApplicationController
before_action :authenticate_customer_support_agent!
def create
user = User.find!(params[:id])
impersonate_user(user)
redirect_to root_path
end
def destroy
# current_user is the impersonated user before we call
# stop_impersonating_user so we should build the notice here or store the
# current_user in a variable so we can fetch values from it later
notice = "You are logged out as #{current_user.email}"
stop_impersonating_user
redirect_to root_path, notice: notice
end
private def authenticate_customer_support_agent!
# If we're already impersonating a user, true_user will be the customer
# support agent
#
# If we're not currently impersonating a user, current_user will be the
# customer support agent
return if (true_user || current_user)&.customer_support_agent?
raise ActionController::RoutingError, 'Not Found'
end
end
لا تنسوا إضافة الـ routes:
Rails.application.routes.draw do
resource :customer_impersonation, only: %i[create destroy]
end
نحتاج الآن إلى إضافة نموذج يسجّل منه المسؤولون الدخول، ويمكننا فعل ذلك بطريقتين.
- نوفّر لموظف دعم العملاء نموذجًا يختار منه المستخدم ويرسله.
- ننشئ زرًا يستخدم طريقة POST لإرسال معرّف المستخدم.
معظم التطبيقات التي عملت عليها تحتوي على صفحة إدارة تعرض جميع المستخدمين المسجلين في جدول مقسّم إلى صفحات، لذا سنختار الخيار 2. وتنفيذه بسيط جدًا بفضل method الجاهزة link_to في Ruby on Rails، إذ يمكننا أن نطلب منها استخدام طريقة POST ثم نمرر معرّف المستخدم إلى المسار.
<table>
<thead>
<tr>
<td>Name</td>
<td>Email</td>
<td></td>
</tr>
</thead>
<tbody>
<% @users.each do |user| %>
<tr>
<td><%= user.name %></td>
<td><%= user.email %></td>
<td>
<%= link_to('Show', user) %>
<%= link_to(
'Impersonate',
customer_impersonation_path(id: user.id),
method: :post
) %>
</td>
</tr>
<% end %>
</tbody>
</table>
إذا كانت لديكم عدة أدوار إدارية، فربما تفكرون في إنشاء pundit policy للتأكد من أن المسؤولين أصحاب الأدوار الصحيحة وحدهم يستطيعون انتحال هوية المستخدمين الآخرين. والآن بعد أن أصبح بإمكاننا انتحال هوية مستخدم، نحتاج أيضًا إلى القدرة على إيقاف ذلك. لدينا بالفعل الـ route والـ action في الـ controller، وبقي أن ننشئ رابطًا يتيح لمنتحلي الهوية تسجيل الخروج فعلًا. وعلينا أيضًا إزالة رابط تسجيل الخروج العادي، وإلا سيُسجَّل خروج المسؤول من حسابه هو. ولواجهة سلسة، يمكننا ببساطة استبدال رابط تسجيل الخروج الحالي برابط إيقاف انتحال الهوية الجديد.
<% if user_signed_in? %>
<%= link_to(
'Log out',
true_user ? customer_impersonation_path : destroy_user_session_path,
method: :delete
) %>
<% end %>
اعتبارات تخص واجهة المستخدم
هناك أمور أنصح بها بشدة لحماية عملائكم من الإجراءات غير المقصودة التي قد يتخذها موظفو الدعم. على الأرجح يتوقع عملاؤكم الخصوصية، لذا قد ترغبون في تبنّي سياسة تشترط طلب إذنهم قبل السماح للموظفين بالدخول إلى حساباتهم. ويمكنكم فعل ذلك عبر المحادثة أو البريد الإلكتروني، أو إنشاء نظام لطلب الإذن يُسجَّل داخل التطبيق. وأنصح بشدة أيضًا بتسجيل كل نشاط يجري أثناء انتحال الهوية. فإنشاء سجل تدقيق (audit trail) طريقة جيدة لحماية أنفسكم من الخطأ الذي سيقع حتمًا في المستقبل. ومع سجل التدقيق ستتمكنون من التحقق من الإجراء الصحيح الواجب اتخاذه حين يثير العميل المشكلة. وفي تطبيقاتنا نذهب أبعد من ذلك، فنسجّل كل طلب يرسله موظف دعم العملاء. لنضف ذلك إلى ApplicationController
bin/rails generate model AuditLog data:jsonb
class AuditLog < ApplicationModel
belongs_to :user
end
class User
has_many :audit_logs
end
class ApplicationController < ActionController::Base
before_action :authenticate_user!
after_action :log_impersonated_activity, if: :true_user
impersonates :user
private def log_impersonated_activity
return unless true_user
true_user.audit_logs.create!(
data: {
controller: params[:controller],
action: params[:action],
method: request.method,
headers: headers,
params: params,
customer_id: current_user.id,
customer_email: current_user.email,
admin_id: true_user.id,
admin_email: true_user.email
}
)
end
end
وأنصح أيضًا بتقييد الإجراءات التي يستطيع موظفو دعم العملاء اتخاذها. نستخدم pundit عادة للتفويض (authorization)، لكن للأسف لم يُصمَّم pundit ليتعامل مع كائنَي مستخدم اثنين. ويمكننا إعادة تعريف pundit_user لنخبر pundit أين يجد كائن المستخدم، أي أن نمرر hash يحتوي على current_user وtrue_user، ثم نحدّث ApplicationPolicy ليعكس هذا التغيير.
class ApplicationController
private def pundit_user
{ current_user: current_user, true_user: true_user }
end
end
class ApplicationPolicy
attr_reader :user, :true_user, :record
def initialize(current_and_true_user, record)
@user = current_and_true_user[:current_user]
@true_user = current_and_true_user[:true_user]
@record = record
end
protected def impersonating_customer?
user.present? && user != true_user
end
end
والآن يمكنكم في الـ policies الأخرى منح صلاحيات مختلفة أثناء وضع انتحال الهوية، كمنع موظفي دعم العملاء من إجراء المدفوعات مثلًا
class PaymentPolicy < ApplicationPolicy
def create?
!impersonating_customer?
end
end
وآخر ما أراه مهمًا هو أن يكون واضحًا تمامًا أن موظف الدعم في وضع انتحال الهوية، مع إضافة بعض الحماية الإضافية من الإجراءات غير المقصودة. أولًا يمكننا إضافة شريط تحذير إلى الصفحة حين يكون موظف دعم العملاء في وضع انتحال الهوية
<% if true_user.present? %>
<div class='banner danger warning'>
You are signed in as <%= current_user.name %>.
<%= link_to 'Stop impersonating', cutomer_impersonations_path(id: current_user.id), method: :delete %>
</div>
<% end %>
ثم يمكننا إضافة رسائل تأكيد إلى كل الإجراءات المهمة:
<%= f.submit 'Save', data: { confirm: "Are you sure you want to save this item for #{current_user.name}'s account" } %>
وأنصح بتعطيل أي إجراءات يجب حمايتها:
<% if true_user %>
<span class='disabled_impersonation'>Link account</span>
<% else %>
<%= link_to 'Link account', link_account_path %>
<% end %>
وبهذا نختتم هذا الدليل. لقد تناولنا كل ما نحتاجه لنتيح لموظفي دعم العملاء تسجيل الدخول إلى حسابات عملائنا.


