feat: gate write operations behind read-only access token scope

This commit is contained in:
Shivam Mishra
2026-05-20 13:19:49 +05:30
parent b96c3df19f
commit e195f9842f
5 changed files with 28 additions and 1 deletions
+18
View File
@@ -1,9 +1,18 @@
class Api::BaseController < ApplicationController
include AccessTokenAuthHelper
# POST-shaped endpoints that are semantically reads and should be reachable
# with a read-only token. Keep this list minimal; expand only when a clear
# read-with-complex-body use case appears.
READ_ONLY_POST_ALLOWLIST = %w[
api/v1/accounts/conversations#filter
api/v1/accounts/contacts#filter
].freeze
respond_to :json
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?
before_action :enforce_read_only_token_scope
private
@@ -11,6 +20,15 @@ class Api::BaseController < ApplicationController
request.headers[:api_access_token].present? || request.headers[:HTTP_API_ACCESS_TOKEN].present?
end
def enforce_read_only_token_scope
return unless @access_token&.scope == 'read_only'
return if request.get? || request.head? || request.options?
return if READ_ONLY_POST_ALLOWLIST.include?("#{params[:controller]}##{params[:action]}")
render json: { error: 'This access token is read-only and cannot perform write operations.' },
status: :forbidden
end
def check_authorization(model = nil)
model ||= controller_name.classify.constantize
@@ -43,6 +43,12 @@ class Api::V1::ProfilesController < Api::BaseController
@user.reload
end
def reset_read_only_access_token
token = @user.read_only_access_token || AccessToken.create!(owner: @user, scope: 'read_only')
token.regenerate_token
@user.reload
end
private
def set_user
+2 -1
View File
@@ -1,4 +1,5 @@
json.access_token resource.access_token.token
json.access_token resource.access_token&.token
json.read_only_access_token resource.read_only_access_token&.token
json.account_id resource.active_account_user&.account_id
json.available_name resource.available_name
json.avatar_url resource.avatar_url
@@ -0,0 +1 @@
json.partial! 'api/v1/models/user', formats: [:json], resource: @user
+1
View File
@@ -422,6 +422,7 @@ Rails.application.routes.draw do
put :set_active_account
post :resend_confirmation
post :reset_access_token
post :reset_read_only_access_token
end
# MFA routes