Class: ApiAuthentication

Inherits:
ApplicationRecord show all
Defined in:
app/models/api_authentication.rb

Overview

== Schema Information

Table name: api_authentications
Database name: primary

id :integer not null, primary key
api_authentication_token :string not null
expires_at :datetime not null
last_used_at :datetime
name :string
permitted_services :text is an Array
revoked_at :datetime
created_at :datetime
account_id :integer not null

Indexes

index_api_authentications_on_account_id (account_id)
index_api_authentications_on_api_authentication_token (api_authentication_token)
index_api_authentications_on_expires_at (expires_at)
index_api_authentications_on_revoked_at (revoked_at)

Constant Summary collapse

EXPIRATION_OPTIONS =

Common expiration options for UI

{
  '1 hour' => 1.hour,
  '1 day' => 1.day,
  '1 week' => 1.week,
  '1 month' => 1.month,
  '3 months' => 3.months,
  '6 months' => 6.months,
  '1 year' => 1.year,
  'Never' => 100.years
}.freeze
UPSTREAM_SERVICES =

MCP gateway services that tokens can grant access to.
The gateway provides content tools only. All other services
(PostgreSQL, Ahrefs, AppSignal, Google Analytics, etc.) are
connected directly as standalone MCP servers in each developer's
Cursor config via script/setup_mcp_servers.sh.

Content-type permissions control access to sensitive embeddings:

  • call_recordings: Search own call recordings (employee's calls only)
  • call_recordings_all: Search ALL call recordings (managers/admins)
{
  'content' => { label: 'Content Tools', description: 'Search, posts, FAQs, products, showcases, images, videos, reviews' },
  'support_cases' => { label: 'Support Cases', description: 'Search support cases, activity notes, and communications for customer issues and resolutions' },
  'call_recordings' => { label: 'My Call Recordings', description: 'Search your own call recording transcripts', sensitive: true },
  'call_recordings_all' => { label: 'All Call Recordings', description: 'Search all employee call recording transcripts (managers only)', sensitive: true },
  'google_analytics' => { label: 'Google Analytics', description: 'GA4 page views, sessions, engagement, traffic sources' },
  'search_console' => { label: 'Search Console', description: 'Google search clicks, impressions, CTR, keyword positions' },
  'google_ads' => { label: 'Google Ads', description: 'Keyword search volume, GAQL queries, campaign data' },
  'ahrefs' => { label: 'Ahrefs SEO', description: 'Backlinks, organic traffic, keyword rankings' },
  'basecamp' => { label: 'Basecamp', description: 'Projects, todos, search, and team collaboration' }
}.freeze
DEFAULT_SERVICES =

Default services.

%w[content].freeze
SENSITIVE_SERVICES =

Services that require explicit permission -- never granted by default or
by the "all services" fallback for OAuth tokens without scoping.

UPSTREAM_SERVICES.select { |_, v| v[:sensitive] }.keys.freeze

Constants included from Models::Schedulable

Models::Schedulable::SIMPLE_FORM_OPTIONS

Instance Attribute Summary collapse

Belongs to collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Models::Schedulable

config

Methods included from Models::AfterCommittable

#after_commit

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#api_authentication_tokenString (readonly)

Returns:

  • (String)

Validations:



75
# File 'app/models/api_authentication.rb', line 75

validates :api_authentication_token, presence: true

#expires_atActiveSupport::TimeWithZone (readonly)

Returns:

  • (ActiveSupport::TimeWithZone)


77
# File 'app/models/api_authentication.rb', line 77

validates :expires_at, presence: true

#expires_inObject

Returns the value of attribute expires_in.



85
86
87
# File 'app/models/api_authentication.rb', line 85

def expires_in
  @expires_in
end

#is_guestObject

Returns the value of attribute is_guest.



85
86
87
# File 'app/models/api_authentication.rb', line 85

def is_guest
  @is_guest
end

Class Method Details

.activeActiveRecord::Relation<ApiAuthentication>

A relation of ApiAuthentications that are active. Active Record Scope

Returns:

See Also:



81
# File 'app/models/api_authentication.rb', line 81

scope :active, -> { where(revoked_at: nil).where('expires_at > ?', Time.current) }

.expiredActiveRecord::Relation<ApiAuthentication>

A relation of ApiAuthentications that are expired. Active Record Scope

Returns:

See Also:



83
# File 'app/models/api_authentication.rb', line 83

scope :expired, -> { where(expires_at: ..Time.current) }

.revokedActiveRecord::Relation<ApiAuthentication>

A relation of ApiAuthentications that are revoked. Active Record Scope

Returns:

See Also:



82
# File 'app/models/api_authentication.rb', line 82

scope :revoked, -> { where.not(revoked_at: nil) }

Instance Method Details

#accountAccount?

Returns:



67
# File 'app/models/api_authentication.rb', line 67

belongs_to :account

#active?Boolean

Returns:

  • (Boolean)


98
99
100
# File 'app/models/api_authentication.rb', line 98

def active?
  !expired? && !revoked?
end

#can_access_service?(service_key) ⇒ Boolean

Check if this token has access to a given upstream service

Parameters:

  • service_key (Object)

Returns:

  • (Boolean)


144
145
146
# File 'app/models/api_authentication.rb', line 144

def can_access_service?(service_key)
  effective_services.include?(service_key.to_s)
end

#effective_servicesObject

Returns the effective services list (with 'content' always included)



149
150
151
152
# File 'app/models/api_authentication.rb', line 149

def effective_services
  services = permitted_services.presence || DEFAULT_SERVICES
  (services | DEFAULT_SERVICES).sort
end

#expired?Boolean

Returns:

  • (Boolean)


88
89
90
# File 'app/models/api_authentication.rb', line 88

def expired?
  expires_at <= Time.current
end

#masked_tokenObject

Masked token for display (only show first/last 4 chars)



125
126
127
128
129
130
131
132
# File 'app/models/api_authentication.rb', line 125

def masked_token
  return nil if api_authentication_token.blank?

  token = api_authentication_token
  return token if token.length <= 12

  "#{token[0..3]}...#{token[-4..]}"
end

#revoke!void

This method returns an undefined value.



103
104
105
# File 'app/models/api_authentication.rb', line 103

def revoke!
  update!(revoked_at: Time.current)
end

#revoked?Boolean

Returns:

  • (Boolean)


93
94
95
# File 'app/models/api_authentication.rb', line 93

def revoked?
  revoked_at.present?
end

#service_labelsObject

Human-readable labels for the permitted services



155
156
157
# File 'app/models/api_authentication.rb', line 155

def service_labels
  effective_services.filter_map { |key| UPSTREAM_SERVICES.dig(key, :label) }
end

#statusvoid

This method returns an undefined value.



108
109
110
111
112
113
# File 'app/models/api_authentication.rb', line 108

def status
  return 'revoked' if revoked?
  return 'expired' if expired?

  'active'
end

#status_badge_classvoid

This method returns an undefined value.



116
117
118
119
120
121
122
# File 'app/models/api_authentication.rb', line 116

def status_badge_class
  case status
  when 'active' then 'bg-success'
  when 'expired' then 'bg-warning'
  when 'revoked' then 'bg-danger'
  end
end

#time_until_expirationObject

Time until expiration in human-readable format



135
136
137
138
139
# File 'app/models/api_authentication.rb', line 135

def time_until_expiration
  return 'Expired' if expired?

  distance_of_time_in_words(Time.current, expires_at)
end