Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 43 additions & 6 deletions engine/app/controllers/coplan/api/v1/base_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,57 @@ module CoPlan
module Api
module V1
class BaseController < ActionController::API
before_action :authenticate_api_token!
before_action :authenticate_api!

private

def authenticate_api_token!
def authenticate_api!
token = request.headers["Authorization"]&.delete_prefix("Bearer ")
@api_token = CoPlan::ApiToken.authenticate(token)
unless @api_token
render json: { error: "Invalid or expired API token" }, status: :unauthorized
if token.present?
authenticate_via_token!(token)
return if @api_token
end

if CoPlan.configuration.api_authenticate
attrs = CoPlan.configuration.api_authenticate.call(request)
if attrs && attrs[:external_id].present?
provision_user_from_hook!(attrs)
return
end
end

render json: { error: "Unauthorized" }, status: :unauthorized
end

def provision_user_from_hook!(attrs)
external_id = attrs[:external_id].to_s
@current_api_user = CoPlan::User.find_or_initialize_by(external_id: external_id)
@current_api_user.assign_attributes(attrs.slice(:name, :admin, :metadata).compact)
if @current_api_user.new_record? || @current_api_user.changed?
@current_api_user.save!
end
rescue ActiveRecord::RecordNotUnique
@current_api_user = CoPlan::User.find_by!(external_id: external_id)
end

def authenticate_via_token!(token)
@api_token = CoPlan::ApiToken.authenticate(token)
end

def current_user
@api_token&.user
@current_api_user || @api_token&.user
end

# Unique identifier for the API caller — used as actor_id, holder_id, author_id.
# With token auth this is the token's ID; with hook auth it's the user's ID.
def api_actor_id
@api_token&.id || @current_api_user&.id
end

# The type of actor making the API call.
# Token auth → "local_agent"; hook auth → "human".
def api_author_type
@api_token ? ApiToken::HOLDER_TYPE : "human"
end

def set_plan
Expand Down
8 changes: 4 additions & 4 deletions engine/app/controllers/coplan/api/v1/comments_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ def create
thread.save!

comment = thread.comments.create!(
author_type: ApiToken::HOLDER_TYPE,
author_id: @api_token.id,
author_type: api_author_type,
author_id: api_actor_id,
body_markdown: params[:body_markdown],
agent_name: params[:agent_name]
)
Expand Down Expand Up @@ -83,8 +83,8 @@ def reply
end

comment = thread.comments.create!(
author_type: ApiToken::HOLDER_TYPE,
author_id: @api_token.id,
author_type: api_author_type,
author_id: api_actor_id,
body_markdown: params[:body_markdown],
agent_name: params[:agent_name]
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def create
lease = EditLease.acquire!(
plan: @plan,
holder_type: ApiToken::HOLDER_TYPE,
holder_id: @api_token.id,
holder_id: api_actor_id,
lease_token: lease_token
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def create
private

def apply_with_session(operations, base_revision)
session = @plan.edit_sessions.find_by(id: params[:session_id], actor_id: @api_token.id)
session = @plan.edit_sessions.find_by(id: params[:session_id], actor_id: api_actor_id)
unless session&.active?
render json: { error: "Edit session not found, expired, or not open" }, status: :not_found
return
Expand Down Expand Up @@ -210,8 +210,8 @@ def commit_version(current_content, result)
plan: @plan,
revision: new_revision,
content_markdown: result[:content],
actor_type: ApiToken::HOLDER_TYPE,
actor_id: @api_token.id,
actor_type: api_author_type,
actor_id: api_actor_id,
change_summary: params[:change_summary],
diff_unified: diff.presence,
operations_json: result[:applied],
Expand Down
4 changes: 2 additions & 2 deletions engine/app/controllers/coplan/api/v1/sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def create
session = EditSession.create!(
plan: @plan,
actor_type: actor_type,
actor_id: @api_token.id,
actor_id: api_actor_id,
base_revision: @plan.current_revision,
expires_at: ttl.from_now
)
Expand Down Expand Up @@ -69,7 +69,7 @@ def commit
private

def set_session
@session = @plan.edit_sessions.find_by(id: params[:id], actor_id: @api_token.id)
@session = @plan.edit_sessions.find_by(id: params[:id], actor_id: api_actor_id)
unless @session
render json: { error: "Edit session not found" }, status: :not_found
end
Expand Down
2 changes: 1 addition & 1 deletion engine/lib/coplan/configuration.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module CoPlan
class Configuration
attr_accessor :authenticate, :sign_in_path
attr_accessor :authenticate, :api_authenticate, :sign_in_path
attr_accessor :ai_base_url, :ai_api_key, :ai_model
attr_accessor :error_reporter
attr_accessor :notification_handler
Expand Down
Loading