Class: OpenLoam::ApiToken

Inherits:
TenantRecord
  • Object
show all
Defined in:
app/models/open_loam/api_token.rb

Overview

A bearer token that lets a machine act as one user in one tenant. Same rules as a human session: whatever the token's user may do in that tenant, no more. Plumbing, so not audited and not evented.

Only the SHA-256 digest is stored. The plaintext is returned once, from the instance that generated it, and is unrecoverable afterwards — a dump of this table is not a set of working credentials. A 24-byte random token needs no slow KDF; it is not guessable the way a password is.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Attribute Details

#token ⇒ Object (readonly)

Present only on the instance that just created it — never after a reload.



20
21
22
# File 'app/models/open_loam/api_token.rb', line 20

def token
  @token
end

Class Method Details

.authenticate(raw_token) ⇒ Object

THE blessed cross-tenant lookup, and the reason it lives in the gem.

A bearer token arrives with no tenant context — the token IS how the request discovers which tenant it belongs to, so this one query must bypass the tenant scope. That is exactly what host apps are forbidden to do (test/open_loam_guardrails_test.rb fails the build on it), so the escape hatch is vetted framework code here, used once, at the edge: find the token, establish OpenLoam::Current, and everything downstream is ordinary tenant-scoped code again.

Returns the token, or nil for an unknown/blank one — callers render 401.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'app/models/open_loam/api_token.rb', line 42

def self.authenticate(raw_token)
  return nil if raw_token.blank?

  api_token = unscoped.find_by(token_digest: digest(raw_token))
  return nil unless api_token

  OpenLoam::Current.tenant = api_token.tenant
  OpenLoam::Current.actor = api_token.user

  # A token outlives the membership that justified it, so offboarding a user
  # from a tenant would otherwise leave their machine access intact. Checked
  # here rather than at revoke time: the membership is the authority.
  unless OpenLoam::Membership.exists?(user_id: api_token.user_id)
    OpenLoam::Current.reset
    return nil
  end

  api_token.update_column(:last_used_at, Time.current)
  api_token
end

.digest(raw_token) ⇒ Object



27
28
29
# File 'app/models/open_loam/api_token.rb', line 27

def self.digest(raw_token)
  OpenSSL::Digest::SHA256.hexdigest(raw_token.to_s)
end