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 =

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 Schedulable

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 Schedulable

config

Methods included from Models::AfterCommittable

#after_commit

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#api_authentication_tokenObject (readonly)



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

validates :api_authentication_token, presence: true

#expires_atObject (readonly)



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

validates :expires_at, presence: true

#expires_inObject

Returns the value of attribute expires_in.



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

def expires_in
  @expires_in
end

#is_guestObject

Returns the value of attribute is_guest.



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

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:



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

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:



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

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

.revokedActiveRecord::Relation<ApiAuthentication>

A relation of ApiAuthentications that are revoked. Active Record Scope

Returns:

See Also:



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

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

Instance Method Details

#accountAccount

Returns:

See Also:



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

belongs_to :account

#active?Boolean

Returns:

  • (Boolean)


91
92
93
# File 'app/models/api_authentication.rb', line 91

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

#can_access_service?(service_key) ⇒ Boolean

Check if this token has access to a given upstream service

Returns:

  • (Boolean)


132
133
134
# File 'app/models/api_authentication.rb', line 132

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)



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

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

#expired?Boolean

Returns:

  • (Boolean)


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

def expired?
  expires_at <= Time.current
end

#masked_tokenObject

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



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

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!Object



95
96
97
# File 'app/models/api_authentication.rb', line 95

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

#revoked?Boolean

Returns:

  • (Boolean)


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

def revoked?
  revoked_at.present?
end

#service_labelsObject

Human-readable labels for the permitted services



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

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

#statusObject



99
100
101
102
103
104
# File 'app/models/api_authentication.rb', line 99

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

  'active'
end

#status_badge_classObject



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

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



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

def time_until_expiration
  return 'Expired' if expired?

  distance_of_time_in_words(Time.current, expires_at)
end