token_authority 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/README.md +199 -7
  4. data/app/controllers/concerns/token_authority/client_authentication.rb +141 -0
  5. data/app/controllers/concerns/token_authority/controller_event_logging.rb +98 -0
  6. data/app/controllers/concerns/token_authority/initial_access_token_authentication.rb +35 -0
  7. data/app/controllers/concerns/token_authority/token_authentication.rb +128 -0
  8. data/app/controllers/token_authority/authorization_grants_controller.rb +119 -0
  9. data/app/controllers/token_authority/authorizations_controller.rb +105 -0
  10. data/app/controllers/token_authority/clients_controller.rb +99 -0
  11. data/app/controllers/token_authority/metadata_controller.rb +12 -0
  12. data/app/controllers/token_authority/resource_metadata_controller.rb +12 -0
  13. data/app/controllers/token_authority/sessions_controller.rb +228 -0
  14. data/app/helpers/token_authority/authorization_grants_helper.rb +27 -0
  15. data/app/models/concerns/token_authority/claim_validatable.rb +95 -0
  16. data/app/models/concerns/token_authority/event_logging.rb +144 -0
  17. data/app/models/concerns/token_authority/resourceable.rb +111 -0
  18. data/app/models/concerns/token_authority/scopeable.rb +105 -0
  19. data/app/models/concerns/token_authority/session_creatable.rb +101 -0
  20. data/app/models/token_authority/access_token.rb +127 -0
  21. data/app/models/token_authority/access_token_request.rb +193 -0
  22. data/app/models/token_authority/authorization_grant.rb +119 -0
  23. data/app/models/token_authority/authorization_request.rb +276 -0
  24. data/app/models/token_authority/authorization_server_metadata.rb +101 -0
  25. data/app/models/token_authority/client.rb +263 -0
  26. data/app/models/token_authority/client_id_resolver.rb +114 -0
  27. data/app/models/token_authority/client_metadata_document.rb +164 -0
  28. data/app/models/token_authority/client_metadata_document_cache.rb +33 -0
  29. data/app/models/token_authority/client_metadata_document_fetcher.rb +266 -0
  30. data/app/models/token_authority/client_registration_request.rb +214 -0
  31. data/app/models/token_authority/client_registration_response.rb +58 -0
  32. data/app/models/token_authority/jwks_cache.rb +37 -0
  33. data/app/models/token_authority/jwks_fetcher.rb +70 -0
  34. data/app/models/token_authority/protected_resource_metadata.rb +74 -0
  35. data/app/models/token_authority/refresh_token.rb +110 -0
  36. data/app/models/token_authority/refresh_token_request.rb +116 -0
  37. data/app/models/token_authority/session.rb +193 -0
  38. data/app/models/token_authority/software_statement.rb +70 -0
  39. data/app/views/token_authority/authorization_grants/new.html.erb +25 -0
  40. data/app/views/token_authority/client_error.html.erb +8 -0
  41. data/config/locales/token_authority.en.yml +248 -0
  42. data/config/routes.rb +29 -0
  43. data/lib/generators/token_authority/install/install_generator.rb +61 -0
  44. data/lib/generators/token_authority/install/templates/create_token_authority_tables.rb.erb +116 -0
  45. data/lib/generators/token_authority/install/templates/token_authority.rb +247 -0
  46. data/lib/token_authority/configuration.rb +397 -0
  47. data/lib/token_authority/engine.rb +34 -0
  48. data/lib/token_authority/errors.rb +221 -0
  49. data/lib/token_authority/instrumentation.rb +80 -0
  50. data/lib/token_authority/instrumentation_log_subscriber.rb +62 -0
  51. data/lib/token_authority/json_web_token.rb +78 -0
  52. data/lib/token_authority/log_event_subscriber.rb +43 -0
  53. data/lib/token_authority/routing/constraints.rb +71 -0
  54. data/lib/token_authority/routing/routes.rb +39 -0
  55. data/lib/token_authority/version.rb +4 -1
  56. data/lib/token_authority.rb +30 -1
  57. metadata +65 -5
  58. data/app/assets/stylesheets/token_authority/application.css +0 -15
  59. data/app/controllers/token_authority/application_controller.rb +0 -4
  60. data/app/helpers/token_authority/application_helper.rb +0 -4
  61. data/app/views/layouts/token_authority/application.html.erb +0 -17
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TokenAuthority
4
+ ##
5
+ # Controller for granting authorization to clients.
6
+ #
7
+ # Inherits from the controller configured via TokenAuthority.config.authenticatable_controller.
8
+ # The authenticatable controller must implement:
9
+ # - authenticate_user! (before_action that ensures user is logged in)
10
+ # - current_user (returns the currently authenticated user)
11
+ #
12
+ # For Devise users, these methods are already available on ApplicationController.
13
+ # For other authentication systems, implement these methods on your authenticatable controller.
14
+ class AuthorizationGrantsController < TokenAuthority.config.authenticatable_controller.constantize
15
+ include TokenAuthority::ControllerEventLogging
16
+
17
+ layout -> { TokenAuthority.config.consent_page_layout }
18
+
19
+ before_action :authenticate_user!
20
+ before_action :set_authorization_request
21
+ before_action :set_token_authority_client
22
+
23
+ rescue_from TokenAuthority::InvalidRedirectUrlError do |error|
24
+ session.delete(:token_authority_internal_state)
25
+ render "token_authority/client_error",
26
+ layout: TokenAuthority.config.error_page_layout,
27
+ status: :bad_request,
28
+ locals: {error_class: error.class, error_message: error.message}
29
+ end
30
+
31
+ def new
32
+ notify_event("authorization.consent.shown",
33
+ client_id: @token_authority_client.public_id,
34
+ client_name: @token_authority_client.name,
35
+ requested_scopes: @authorization_request.scope)
36
+
37
+ render :new, locals: {
38
+ client_name: @token_authority_client.name,
39
+ resources: @authorization_request.resources,
40
+ scopes: @authorization_request.scope
41
+ }
42
+ end
43
+
44
+ def create
45
+ state = @authorization_request.state
46
+
47
+ unless ActiveModel::Type::Boolean.new.cast(params[:approve])
48
+ notify_event("authorization.consent.denied",
49
+ client_id: @token_authority_client.public_id)
50
+
51
+ redirect_to_client(params_for_redirect: {error: "access_denied", state:}) and return
52
+ end
53
+
54
+ notify_event("authorization.consent.granted",
55
+ client_id: @token_authority_client.public_id,
56
+ granted_scopes: @authorization_request.scope)
57
+
58
+ grant = @token_authority_client.new_authorization_grant(
59
+ user: current_user,
60
+ challenge_params: {
61
+ code_challenge: @authorization_request.code_challenge,
62
+ code_challenge_method: @authorization_request.code_challenge_method,
63
+ redirect_uri: @authorization_request.redirect_uri,
64
+ resources: @authorization_request.resources,
65
+ scopes: @authorization_request.scope
66
+ }
67
+ )
68
+
69
+ if grant.persisted?
70
+ notify_event("authorization.grant.created",
71
+ grant_id: grant.public_id,
72
+ client_id: @token_authority_client.public_id,
73
+ expires_at: grant.expires_at&.iso8601,
74
+ scopes: grant.scopes)
75
+
76
+ redirect_to_client(params_for_redirect: {code: grant.public_id, state:}) and return
77
+ end
78
+
79
+ redirect_to_client(params_for_redirect: {error: "invalid_request", state:})
80
+ end
81
+
82
+ private
83
+
84
+ def set_authorization_request
85
+ internal_state = session[:token_authority_internal_state]
86
+
87
+ if internal_state.blank?
88
+ render_state_error("Authorization state not found")
89
+ return
90
+ end
91
+
92
+ @authorization_request = TokenAuthority::AuthorizationRequest.from_internal_state_token(internal_state)
93
+ rescue JWT::DecodeError, JWT::ExpiredSignature
94
+ clear_internal_state
95
+ render_state_error("Invalid or expired authorization state")
96
+ end
97
+
98
+ def set_token_authority_client
99
+ @token_authority_client = @authorization_request.token_authority_client
100
+ end
101
+
102
+ def redirect_to_client(params_for_redirect:)
103
+ clear_internal_state
104
+ url = @token_authority_client.url_for_redirect(params: params_for_redirect.compact)
105
+ redirect_to url, allow_other_host: true
106
+ end
107
+
108
+ def clear_internal_state
109
+ session.delete(:token_authority_internal_state)
110
+ end
111
+
112
+ def render_state_error(message)
113
+ render "token_authority/client_error",
114
+ layout: TokenAuthority.config.error_page_layout,
115
+ status: :bad_request,
116
+ locals: {error_class: "InvalidStateError", error_message: message}
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TokenAuthority
4
+ # Handles OAuth 2.1 authorization requests (GET /authorize).
5
+ #
6
+ # This controller implements the initial step of the authorization code flow.
7
+ # It validates the authorization request parameters, authenticates the client,
8
+ # and redirects to the consent screen if everything is valid.
9
+ #
10
+ # The controller emits structured events for monitoring authorization requests,
11
+ # validation failures, and error conditions. Error responses follow OAuth 2.1
12
+ # specifications, redirecting to the client's redirect_uri when possible.
13
+ #
14
+ # @example Authorization request
15
+ # GET /authorize?client_id=abc123
16
+ # &redirect_uri=https://app.example.com/callback
17
+ # &response_type=code
18
+ # &state=xyz
19
+ # &code_challenge=E9Melhoa...
20
+ # &code_challenge_method=S256
21
+ # &scope=read+write
22
+ #
23
+ # @since 0.2.0
24
+ class AuthorizationsController < ActionController::Base
25
+ include TokenAuthority::ClientAuthentication
26
+ include TokenAuthority::ControllerEventLogging
27
+
28
+ before_action :authenticate_client
29
+
30
+ rescue_from TokenAuthority::InvalidRedirectUrlError do |error|
31
+ render "token_authority/client_error",
32
+ layout: TokenAuthority.config.error_page_layout,
33
+ status: :bad_request,
34
+ locals: {error_class: error.class, error_message: error.message}
35
+ end
36
+
37
+ def authorize
38
+ state = params[:state]
39
+ resources = Array(params[:resource]).presence || []
40
+
41
+ notify_event("authorization.request.received",
42
+ client_id: params[:client_id],
43
+ client_type: @token_authority_client&.client_type,
44
+ redirect_uri: params[:redirect_uri],
45
+ has_pkce: params[:code_challenge].present?,
46
+ requested_scopes: params[:scope],
47
+ requested_resources: resources)
48
+
49
+ authorization_request = @token_authority_client.new_authorization_request(
50
+ client_id: params[:client_id],
51
+ code_challenge: params[:code_challenge],
52
+ code_challenge_method: params[:code_challenge_method],
53
+ redirect_uri: params[:redirect_uri],
54
+ response_type: params[:response_type],
55
+ state:,
56
+ resources:,
57
+ scope: params[:scope]
58
+ )
59
+
60
+ if authorization_request.valid?
61
+ notify_event("authorization.request.validated",
62
+ client_id: params[:client_id],
63
+ validated_scopes: authorization_request.scope,
64
+ validated_resources: authorization_request.resources)
65
+
66
+ session[:token_authority_internal_state] = authorization_request.to_internal_state_token
67
+ redirect_to new_authorization_grant_path
68
+ elsif authorization_request.errors.where(:redirect_uri).any?
69
+ notify_event("authorization.request.failed",
70
+ client_id: params[:client_id],
71
+ error_type: "invalid_redirect_uri",
72
+ validation_errors: authorization_request.errors.full_messages)
73
+
74
+ head :bad_request and return
75
+ elsif authorization_request.errors.where(:resources).any?
76
+ notify_event("authorization.request.failed",
77
+ client_id: params[:client_id],
78
+ error_type: "invalid_target",
79
+ validation_errors: authorization_request.errors.full_messages)
80
+
81
+ params_for_redirect = {error: :invalid_target, state:}.compact
82
+ url = @token_authority_client.url_for_redirect(params: params_for_redirect.compact)
83
+ redirect_to url, allow_other_host: true
84
+ elsif authorization_request.errors.where(:scope).any?
85
+ notify_event("authorization.request.failed",
86
+ client_id: params[:client_id],
87
+ error_type: "invalid_scope",
88
+ validation_errors: authorization_request.errors.full_messages)
89
+
90
+ params_for_redirect = {error: :invalid_scope, state:}.compact
91
+ url = @token_authority_client.url_for_redirect(params: params_for_redirect.compact)
92
+ redirect_to url, allow_other_host: true
93
+ else
94
+ notify_event("authorization.request.failed",
95
+ client_id: params[:client_id],
96
+ error_type: "invalid_request",
97
+ validation_errors: authorization_request.errors.full_messages)
98
+
99
+ params_for_redirect = {error: :invalid_request, state:}.compact
100
+ url = @token_authority_client.url_for_redirect(params: params_for_redirect.compact)
101
+ redirect_to url, allow_other_host: true
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TokenAuthority
4
+ ##
5
+ # Controller for dynamic client registration (RFC 7591)
6
+ class ClientsController < ActionController::API
7
+ include TokenAuthority::InitialAccessTokenAuthentication
8
+ include TokenAuthority::ControllerEventLogging
9
+
10
+ rescue_from TokenAuthority::InvalidClientMetadataError do |error|
11
+ notify_event("client.registration.failed",
12
+ error_type: "invalid_client_metadata",
13
+ validation_errors: [error.message])
14
+
15
+ render_registration_error("invalid_client_metadata", error.message)
16
+ end
17
+
18
+ rescue_from TokenAuthority::InvalidSoftwareStatementError do |error|
19
+ notify_event("client.registration.failed",
20
+ error_type: "invalid_software_statement",
21
+ validation_errors: [error.message])
22
+
23
+ render_registration_error("invalid_software_statement", error.message)
24
+ end
25
+
26
+ rescue_from TokenAuthority::UnapprovedSoftwareStatementError do |error|
27
+ notify_event("client.registration.failed",
28
+ error_type: "unapproved_software_statement",
29
+ validation_errors: [error.message])
30
+
31
+ render_registration_error("unapproved_software_statement", error.message)
32
+ end
33
+
34
+ rescue_from TokenAuthority::InvalidInitialAccessTokenError do
35
+ notify_event("client.registration.failed",
36
+ error_type: "invalid_token",
37
+ validation_errors: ["Initial access token is invalid or missing"])
38
+
39
+ render_registration_error("invalid_token", "Initial access token is invalid or missing", status: :unauthorized)
40
+ end
41
+
42
+ rescue_from ActiveRecord::RecordInvalid do |error|
43
+ notify_event("client.registration.failed",
44
+ error_type: "invalid_client_metadata",
45
+ validation_errors: error.record.errors.full_messages)
46
+
47
+ render_registration_error("invalid_client_metadata", error.record.errors.full_messages.join(", "))
48
+ end
49
+
50
+ def create
51
+ request = ClientRegistrationRequest.new(registration_params)
52
+
53
+ if request.valid?
54
+ client = request.create_client!
55
+
56
+ notify_event("client.registration.completed",
57
+ client_id: client.public_id,
58
+ client_name: client.name,
59
+ client_type: client.client_type,
60
+ grant_types: client.grant_types)
61
+
62
+ render json: ClientRegistrationResponse.new(client:).to_h, status: :created
63
+ else
64
+ notify_event("client.registration.failed",
65
+ error_type: "invalid_client_metadata",
66
+ validation_errors: request.errors.full_messages)
67
+
68
+ render_registration_error("invalid_client_metadata", request.errors.full_messages.join(", "))
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def registration_params
75
+ params.permit(
76
+ :token_endpoint_auth_method,
77
+ :client_name,
78
+ :client_uri,
79
+ :logo_uri,
80
+ :tos_uri,
81
+ :policy_uri,
82
+ :scope,
83
+ :jwks_uri,
84
+ :software_id,
85
+ :software_version,
86
+ :software_statement,
87
+ redirect_uris: [],
88
+ grant_types: [],
89
+ response_types: [],
90
+ contacts: [],
91
+ jwks: {}
92
+ )
93
+ end
94
+
95
+ def render_registration_error(error, error_description, status: :bad_request)
96
+ render json: {error:, error_description:}, status:
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TokenAuthority
4
+ ##
5
+ # Controller for RFC 8414 OAuth 2.0 Authorization Server Metadata
6
+ class MetadataController < ActionController::API
7
+ def show
8
+ metadata = AuthorizationServerMetadata.new(mount_path: params[:mount_path])
9
+ render json: metadata.to_h
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TokenAuthority
4
+ ##
5
+ # Controller for RFC 9728 OAuth 2.0 Protected Resource Metadata
6
+ class ResourceMetadataController < ActionController::API
7
+ def show
8
+ metadata = ProtectedResourceMetadata.new(mount_path: params[:mount_path])
9
+ render json: metadata.to_h
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,228 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TokenAuthority
4
+ ##
5
+ # Controller for issuing access and refresh tokens.
6
+ class SessionsController < ActionController::API
7
+ include TokenAuthority::ClientAuthentication
8
+ include TokenAuthority::ControllerEventLogging
9
+
10
+ before_action :set_authorization_grant, only: :token
11
+ before_action :authenticate_client, except: :unsupported_grant_type
12
+
13
+ rescue_from TokenAuthority::InvalidGrantError do
14
+ notify_event("token.exchange.failed",
15
+ client_id: params[:client_id],
16
+ error_type: "invalid_grant",
17
+ validation_errors: ["Authorization grant is invalid or expired"])
18
+
19
+ render_token_request_error(error: "invalid_grant")
20
+ end
21
+
22
+ rescue_from TokenAuthority::ServerError do |error|
23
+ Rails.logger.error(error.message)
24
+ render_token_request_error(error: "server_error", status: :internal_server_error)
25
+ end
26
+
27
+ def token
28
+ resources = Array(params[:resource]).presence || []
29
+
30
+ notify_event("token.exchange.requested",
31
+ client_id: params[:client_id],
32
+ grant_id: @authorization_grant&.public_id,
33
+ has_code_verifier: params[:code_verifier].present?)
34
+
35
+ access_token_request = TokenAuthority::AccessTokenRequest.new(
36
+ token_authority_authorization_grant: @authorization_grant,
37
+ code_verifier: params[:code_verifier],
38
+ redirect_uri: params[:redirect_uri],
39
+ resources:,
40
+ scope: params[:scope]
41
+ )
42
+
43
+ if access_token_request.valid?
44
+ access_token, refresh_token, expiration, scope, token_authority_session = @authorization_grant.redeem(
45
+ resources: access_token_request.effective_resources,
46
+ scopes: access_token_request.effective_scopes
47
+ ).deconstruct
48
+
49
+ notify_event("token.exchange.completed",
50
+ client_id: params[:client_id],
51
+ session_id: token_authority_session&.id,
52
+ expires_in: expiration)
53
+
54
+ response_body = {access_token:, refresh_token:, token_type: "bearer", expires_in: expiration, scope:}.compact
55
+ render json: response_body
56
+ elsif access_token_request.errors.where(:resources).any?
57
+ notify_event("token.exchange.failed",
58
+ client_id: params[:client_id],
59
+ error_type: "invalid_target",
60
+ validation_errors: access_token_request.errors.full_messages)
61
+
62
+ render_token_request_error(error: "invalid_target")
63
+ elsif access_token_request.errors.where(:scope).any?
64
+ notify_event("token.exchange.failed",
65
+ client_id: params[:client_id],
66
+ error_type: "invalid_scope",
67
+ validation_errors: access_token_request.errors.full_messages)
68
+
69
+ render_token_request_error(error: "invalid_scope")
70
+ else
71
+ notify_event("token.exchange.failed",
72
+ client_id: params[:client_id],
73
+ error_type: "invalid_request",
74
+ validation_errors: access_token_request.errors.full_messages)
75
+
76
+ render_token_request_error(error: "invalid_request")
77
+ end
78
+ end
79
+
80
+ def refresh
81
+ token = TokenAuthority::RefreshToken.from_token(params[:refresh_token])
82
+ resources = Array(params[:resource]).presence || []
83
+
84
+ refresh_token_request = TokenAuthority::RefreshTokenRequest.new(
85
+ token:,
86
+ client_id: params[:client_id],
87
+ resources:,
88
+ scope: params[:scope]
89
+ )
90
+
91
+ old_session = refresh_token_request.token_authority_session
92
+
93
+ notify_event("token.refresh.requested",
94
+ client_id: params[:client_id],
95
+ session_id: old_session&.id)
96
+
97
+ if refresh_token_request.valid?
98
+ access_token, refresh_token, expiration, scope, new_session = old_session.refresh(
99
+ token:,
100
+ client_id: refresh_token_request.resolved_client_id,
101
+ resources: refresh_token_request.effective_resources,
102
+ scopes: refresh_token_request.effective_scopes
103
+ ).deconstruct
104
+
105
+ notify_event("token.refresh.completed",
106
+ client_id: params[:client_id],
107
+ old_session_id: old_session&.id,
108
+ new_session_id: new_session&.id)
109
+
110
+ response_body = {access_token:, refresh_token:, token_type: "bearer", expires_in: expiration, scope:}.compact
111
+ render json: response_body
112
+ elsif refresh_token_request.errors.where(:resources).any?
113
+ notify_event("token.refresh.failed",
114
+ client_id: params[:client_id],
115
+ error_type: "invalid_target",
116
+ validation_errors: refresh_token_request.errors.full_messages)
117
+
118
+ render_token_request_error(error: "invalid_target")
119
+ elsif refresh_token_request.errors.where(:scope).any?
120
+ notify_event("token.refresh.failed",
121
+ client_id: params[:client_id],
122
+ error_type: "invalid_scope",
123
+ validation_errors: refresh_token_request.errors.full_messages)
124
+
125
+ render_token_request_error(error: "invalid_scope")
126
+ else
127
+ notify_event("token.refresh.failed",
128
+ client_id: params[:client_id],
129
+ error_type: "invalid_request",
130
+ validation_errors: refresh_token_request.errors.full_messages)
131
+
132
+ render_token_request_error(error: "invalid_request")
133
+ end
134
+ rescue JWT::DecodeError
135
+ notify_event("token.refresh.failed",
136
+ client_id: params[:client_id],
137
+ error_type: "invalid_request",
138
+ validation_errors: ["Invalid refresh token format"])
139
+
140
+ render_token_request_error(error: "invalid_request")
141
+ rescue TokenAuthority::RevokedSessionError => error
142
+ notify_event("security.token.theft_detected",
143
+ client_id: error.client_id,
144
+ refreshed_session_id: error.refreshed_session_id,
145
+ revoked_session_id: error.revoked_session_id)
146
+
147
+ Rails.logger.warn(error.message)
148
+ render_token_request_error(error: "invalid_request")
149
+ end
150
+
151
+ def unsupported_grant_type
152
+ render_token_request_error(error: "unsupported_grant_type")
153
+ end
154
+
155
+ def revoke
156
+ notify_event("token.revocation.requested",
157
+ client_id: @token_authority_client&.public_id,
158
+ type_hint: params[:token_type_hint])
159
+
160
+ token = TokenAuthority::JsonWebToken.decode(params[:token])
161
+ token_authority_session = TokenAuthority::Session.find_by(access_token_jti: token[:jti]) ||
162
+ TokenAuthority::Session.find_by(refresh_token_jti: token[:jti])
163
+
164
+ TokenAuthority::Session.revoke_for_token(jti: token[:jti])
165
+
166
+ notify_event("token.revocation.completed",
167
+ client_id: @token_authority_client&.public_id,
168
+ session_id: token_authority_session&.id)
169
+
170
+ head :ok
171
+ rescue JWT::DecodeError
172
+ render_unsupported_token_type_error
173
+ end
174
+
175
+ def revoke_access_token
176
+ notify_event("token.revocation.requested",
177
+ client_id: @token_authority_client&.public_id,
178
+ type_hint: "access_token")
179
+
180
+ token = TokenAuthority::AccessToken.from_token(params[:token])
181
+ token_authority_session = TokenAuthority::Session.find_by(access_token_jti: token.jti)
182
+
183
+ TokenAuthority::Session.revoke_for_access_token(access_token_jti: token.jti)
184
+
185
+ notify_event("token.revocation.completed",
186
+ client_id: @token_authority_client&.public_id,
187
+ session_id: token_authority_session&.id)
188
+
189
+ head :ok
190
+ rescue JWT::DecodeError
191
+ render_unsupported_token_type_error
192
+ end
193
+
194
+ def revoke_refresh_token
195
+ notify_event("token.revocation.requested",
196
+ client_id: @token_authority_client&.public_id,
197
+ type_hint: "refresh_token")
198
+
199
+ token = TokenAuthority::RefreshToken.from_token(params[:token])
200
+ token_authority_session = TokenAuthority::Session.find_by(refresh_token_jti: token.jti)
201
+
202
+ TokenAuthority::Session.revoke_for_refresh_token(refresh_token_jti: token.jti)
203
+
204
+ notify_event("token.revocation.completed",
205
+ client_id: @token_authority_client&.public_id,
206
+ session_id: token_authority_session&.id)
207
+
208
+ head :ok
209
+ rescue JWT::DecodeError
210
+ render_unsupported_token_type_error
211
+ end
212
+
213
+ private
214
+
215
+ def set_authorization_grant
216
+ @authorization_grant = TokenAuthority::AuthorizationGrant.find_by(public_id: params[:code])
217
+ raise TokenAuthority::InvalidGrantError if @authorization_grant.blank? || @authorization_grant.redeemed?
218
+ end
219
+
220
+ def render_token_request_error(error:, status: :bad_request)
221
+ render json: {error:}, status:
222
+ end
223
+
224
+ def render_unsupported_token_type_error
225
+ render json: {error: "unsupported_token_type"}, status: :bad_request
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TokenAuthority
4
+ module AuthorizationGrantsHelper
5
+ # Returns a human-friendly display name for a resource URI.
6
+ # Looks up the URI in the configured rfc_8707_resources mapping.
7
+ # Falls back to the URI itself if no mapping is configured.
8
+ #
9
+ # @param resource_uri [String] The resource URI
10
+ # @return [String] The display name or the URI if no mapping exists
11
+ def resource_display_name(resource_uri)
12
+ resources = TokenAuthority.config.rfc_8707_resources || {}
13
+ resources[resource_uri] || resource_uri
14
+ end
15
+
16
+ # Returns a human-friendly display name for a scope.
17
+ # Looks up the scope in the configured scopes mapping.
18
+ # Falls back to the scope itself if no mapping is configured.
19
+ #
20
+ # @param scope [String] The scope string
21
+ # @return [String] The display name or the scope if no mapping exists
22
+ def scope_display_name(scope)
23
+ scopes = TokenAuthority.config.scopes || {}
24
+ scopes[scope] || scope
25
+ end
26
+ end
27
+ end