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' },
  '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 =
%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

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::EventPublishable

#publish_event

Instance Attribute Details

#api_authentication_tokenObject (readonly)



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

validates :api_authentication_token, presence: true

#expires_atObject (readonly)



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

validates :expires_at, presence: true

#expires_inObject

Returns the value of attribute expires_in.



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

def expires_in
  @expires_in
end

#is_guestObject

Returns the value of attribute is_guest.



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

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:



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

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:



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

scope :expired, -> { where('expires_at <= ?', Time.current) }

.revokedActiveRecord::Relation<ApiAuthentication>

A relation of ApiAuthentications that are revoked. Active Record Scope

Returns:

See Also:



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

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

Instance Method Details

#accountAccount

Returns:

See Also:



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

belongs_to :account

#active?Boolean

Returns:

  • (Boolean)


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

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

#can_access_service?(service_key) ⇒ Boolean

Check if this token has access to a given upstream service

Returns:

  • (Boolean)


130
131
132
# File 'app/models/api_authentication.rb', line 130

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)



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

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

#expired?Boolean

Returns:

  • (Boolean)


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

def expired?
  expires_at <= Time.current
end

#masked_tokenObject

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



113
114
115
116
117
118
119
120
# File 'app/models/api_authentication.rb', line 113

def masked_token
  return nil unless api_authentication_token.present?

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

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

#revoke!Object



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

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

#revoked?Boolean

Returns:

  • (Boolean)


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

def revoked?
  revoked_at.present?
end

#service_labelsObject

Human-readable labels for the permitted services



141
142
143
# File 'app/models/api_authentication.rb', line 141

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

#statusObject



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

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

  'active'
end

#status_badge_classObject



104
105
106
107
108
109
110
# File 'app/models/api_authentication.rb', line 104

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



123
124
125
126
127
# File 'app/models/api_authentication.rb', line 123

def time_until_expiration
  return 'Expired' if expired?

  distance_of_time_in_words(Time.current, expires_at)
end