Module: Tep::AuthOAuth2

Defined in:
lib/tep/auth_oauth2.rb

Constant Summary collapse

DEFAULT_CODE_TTL =

Default code TTL (seconds). Apps that need shorter / longer pass an explicit ttl_seconds to issue_code.

600
DEFAULT_TOKEN_TTL =

Default token TTL (seconds). The JWT exp claim is set to ‘now + this`. Apps that need a different window pass an explicit token_ttl_seconds to exchange_code.

3600

Class Method Summary collapse

Class Method Details

.exchange_code(code, client_id, token_ttl_seconds) ⇒ Object

Redeem a code for a JWT. The code MUST have been issued for this exact client_id (no cross-client redemption). Returns the JWT string on success, “” on failure (unknown code, client_id mismatch, expired, already-redeemed).

The JWT is single-use against the registry: a successful exchange_code removes the code from the registry.

‘token_ttl_seconds` is the JWT’s exp lifetime; pass 0 for DEFAULT_TOKEN_TTL.



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/tep/auth_oauth2.rb', line 122

def self.exchange_code(code, client_id, token_ttl_seconds)
  Tep::AuthOAuth2.sweep_expired_codes
  codes = Tep::APP.auth_oauth2_codes
  idx = -1
  i = 0
  while i < codes.length
    if codes[i].code == code && codes[i].client_id == client_id
      idx = i
      i = codes.length
    else
      i += 1
    end
  end
  if idx < 0
    return ""
  end
  rec = codes[idx]
  codes.delete_at(idx)
  if rec.expired?(Time.now.to_i)
    return ""
  end
  Tep::AuthOAuth2.mint_jwt(rec, token_ttl_seconds)
end

.find_client(client_id) ⇒ Object



75
76
77
78
79
80
81
82
83
84
85
# File 'lib/tep/auth_oauth2.rb', line 75

def self.find_client(client_id)
  clients = Tep::APP.auth_oauth2_clients
  i = 0
  while i < clients.length
    if clients[i].client_id == client_id
      return clients[i]
    end
    i += 1
  end
  nil
end

.issue_code(principal_id, client_id, caps_str, ttl_seconds) ⇒ Object

Mint a one-time code tied to (principal, client, granted_caps). Caller (the app’s /authorize handler) is responsible for validating that granted_caps is a subset of the client’s allowed_caps before calling – the issuance surface itself trusts the caller.

‘caps_str` is comma-separated (matches Tep::AuthBearerToken’s wire format). ‘ttl_seconds` is the lifetime; pass 0 for DEFAULT_CODE_TTL.

Returns the opaque code string (base64url, ~32 chars).



98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/tep/auth_oauth2.rb', line 98

def self.issue_code(principal_id, client_id, caps_str, ttl_seconds)
  Tep::AuthOAuth2.sweep_expired_codes
  ttl = ttl_seconds
  if ttl <= 0
    ttl = DEFAULT_CODE_TTL
  end
  code = Crypto.sp_crypto_random_b64url(24)
  expires_at = Time.now.to_i + ttl
  rec = Tep::AuthOAuth2Code.new(
    code, principal_id, client_id, caps_str, expires_at)
  Tep::APP.auth_oauth2_codes.push(rec)
  code
end

.mint_jwt(rec, token_ttl_seconds) ⇒ Object

Build the JWT payload and sign it. Uses Tep::Jwt with the same shared secret as Tep::AuthBearerToken, so apps don’t need to manage a second secret – one HS256 secret signs all tokens regardless of issuance path.



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/tep/auth_oauth2.rb', line 150

def self.mint_jwt(rec, token_ttl_seconds)
  secret = Tep::APP.auth_bearer_secret
  if secret.length == 0
    return ""
  end
  ttl = token_ttl_seconds
  if ttl <= 0
    ttl = DEFAULT_TOKEN_TTL
  end
  now_ts = Time.now.to_i
  exp_ts = now_ts + ttl
  delegate_str = rec.client_id + "|" + now_ts.to_s + "|" +
                 exp_ts.to_s + "|oauth_grant"
  payload = "{" +
    Tep::Json.encode_pair_str("sub", rec.principal_id) + "," +
    Tep::Json.encode_pair_int("exp", exp_ts) + "," +
    Tep::Json.encode_pair_str("caps", rec.caps_str) + "," +
    Tep::Json.encode_pair_str("delegate", delegate_str) +
  "}"
  Tep::Jwt.encode_hs256(payload, secret)
end

.register_client(client_id, name, redirect_uri, allowed_caps) ⇒ Object

Register a client (bot / agent / automation peer) with the authorization server. Subsequent issue_code and exchange_code calls reference it by client_id. Re-registering an existing client_id replaces the prior entry.



54
55
56
57
58
59
60
# File 'lib/tep/auth_oauth2.rb', line 54

def self.register_client(client_id, name, redirect_uri, allowed_caps)
  Tep::AuthOAuth2.unregister_client(client_id)
  client = Tep::AuthOAuth2Client.new(
    client_id, name, redirect_uri, allowed_caps)
  Tep::APP.auth_oauth2_clients.push(client)
  0
end

.sweep_expired_codesObject

Walk the code registry, drop entries whose expires_at has passed. Called on every issue / exchange so the registry doesn’t grow unboundedly even without explicit pruning. Back-to-front so delete_at indices stay valid mid-loop.



176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/tep/auth_oauth2.rb', line 176

def self.sweep_expired_codes
  codes = Tep::APP.auth_oauth2_codes
  now_ts = Time.now.to_i
  i = codes.length - 1
  while i >= 0
    if codes[i].expired?(now_ts)
      codes.delete_at(i)
    end
    i -= 1
  end
  0
end

.unregister_client(client_id) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/tep/auth_oauth2.rb', line 62

def self.unregister_client(client_id)
  clients = Tep::APP.auth_oauth2_clients
  i = 0
  while i < clients.length
    if clients[i].client_id == client_id
      clients.delete_at(i)
      return 0
    end
    i += 1
  end
  0
end