Class: StandardId::Api::Oauth::RevocationsController
- Inherits:
-
BaseController
- Object
- ActionController::API
- BaseController
- BaseController
- StandardId::Api::Oauth::RevocationsController
- Defined in:
- app/controllers/standard_id/api/oauth/revocations_controller.rb
Constant Summary
Constants included from RateLimitHandling
RateLimitHandling::RATE_LIMIT_STORE
Instance Method Summary collapse
-
#create ⇒ Object
POST /oauth/revoke RFC 7009 - OAuth 2.0 Token Revocation.
Methods included from ControllerPolicy
all_controllers, authenticated_controllers, public_controllers, register, registry_snapshot, reset_registry!
Instance Method Details
#create ⇒ Object
POST /oauth/revoke RFC 7009 - OAuth 2.0 Token Revocation
Accepts a token and optional token_type_hint parameter. Always responds with 200 OK regardless of whether the token was valid or revocation was successful (per RFC 7009 Section 2.1).
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'app/controllers/standard_id/api/oauth/revocations_controller.rb', line 15 def create token = params[:token] head :ok and return if token.blank? payload = StandardId::JwtService.decode(token) head :ok and return unless payload&.dig(:sub) account_id = payload[:sub] sessions = StandardId::DeviceSession .where(account_id: account_id) .active # token_type_hint is accepted but ignored — we always attempt # revocation via sub claim regardless of token type (RFC 7009 §2.1) revoked_sessions = sessions.to_a if revoked_sessions.any? now = Time.current session_ids = revoked_sessions.map(&:id) # Bulk-revoke in two queries (one UPDATE per table) instead of # issuing session.revoke! per row, which would be O(N) UPDATEs plus # another O(N) cascades to refresh_tokens. # # Tradeoff: update_all skips ActiveRecord callbacks, so the per-row # SESSION_REVOKED event emitted by Session#revoke! is not fired # automatically. We re-emit it explicitly below so audit-trail # subscribers (account status/locking, etc.) still see one event # per revoked session — the semantics are preserved, only the SQL # shape has changed. ActiveRecord::Base.transaction do StandardId::Session.where(id: session_ids).update_all(revoked_at: now) StandardId::RefreshToken .where(session_id: session_ids, revoked_at: nil) .update_all(revoked_at: now) end # DB state is already committed above; event publishing is best-effort # audit emission. A failing subscriber must not short-circuit the loop # and leave later sessions without their SESSION_REVOKED event, which # would permanently desync audit-trail consumers from the DB. # # All revoked_sessions share the same account_id (we filtered by it # at line 25), so we load the account once rather than calling # session.account per row, which would issue N extra SELECTs. shared_account = revoked_sessions.first.account revoked_sessions.each do |session| session.revoked_at = now begin StandardId::Events.publish( StandardId::Events::SESSION_REVOKED, session: session, account: shared_account, reason: "token_revocation" ) rescue StandardError => e StandardId.logger.error( "[StandardId::Revocations] Failed to publish SESSION_REVOKED " \ "for session #{session.id}: #{e.class}: #{e.}" ) end end begin StandardId::Events.publish( StandardId::Events::OAUTH_TOKEN_REVOKED, account_id: account_id, sessions_revoked: revoked_sessions.size ) rescue StandardError => e StandardId.logger.error( "[StandardId::Revocations] Failed to publish OAUTH_TOKEN_REVOKED " \ "for account #{account_id}: #{e.class}: #{e.}" ) end end head :ok end |