rodauth-oauth 0.10.4 → 1.0.0.pre.beta2
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.
- checksums.yaml +4 -4
 - data/MIGRATION-GUIDE-v1.md +286 -0
 - data/README.md +28 -35
 - data/doc/release_notes/1_0_0_beta1.md +38 -0
 - data/doc/release_notes/1_0_0_beta2.md +34 -0
 - data/lib/generators/rodauth/oauth/install_generator.rb +0 -1
 - data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +21 -11
 - data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_search.html.erb +1 -1
 - data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_verification.html.erb +2 -2
 - data/lib/generators/rodauth/oauth/templates/app/views/rodauth/new_oauth_application.html.erb +1 -6
 - data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application.html.erb +0 -2
 - data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_grants.html.erb +41 -0
 - data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_applications.html.erb +2 -2
 - data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_grants.html.erb +37 -0
 - data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +57 -57
 - data/lib/rodauth/features/oauth_application_management.rb +61 -74
 - data/lib/rodauth/features/oauth_assertion_base.rb +19 -23
 - data/lib/rodauth/features/oauth_authorization_code_grant.rb +62 -90
 - data/lib/rodauth/features/oauth_authorize_base.rb +115 -22
 - data/lib/rodauth/features/oauth_base.rb +397 -315
 - data/lib/rodauth/features/oauth_client_credentials_grant.rb +20 -18
 - data/lib/rodauth/features/{oauth_device_grant.rb → oauth_device_code_grant.rb} +62 -73
 - data/lib/rodauth/features/oauth_dynamic_client_registration.rb +52 -31
 - data/lib/rodauth/features/oauth_grant_management.rb +70 -0
 - data/lib/rodauth/features/oauth_implicit_grant.rb +29 -27
 - data/lib/rodauth/features/oauth_jwt.rb +53 -689
 - data/lib/rodauth/features/oauth_jwt_base.rb +458 -0
 - data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +48 -17
 - data/lib/rodauth/features/oauth_jwt_jwks.rb +47 -0
 - data/lib/rodauth/features/oauth_jwt_secured_authorization_request.rb +116 -0
 - data/lib/rodauth/features/oauth_management_base.rb +2 -0
 - data/lib/rodauth/features/oauth_pkce.rb +22 -26
 - data/lib/rodauth/features/oauth_resource_indicators.rb +33 -25
 - data/lib/rodauth/features/oauth_resource_server.rb +59 -0
 - data/lib/rodauth/features/oauth_saml_bearer_grant.rb +7 -1
 - data/lib/rodauth/features/oauth_token_introspection.rb +76 -46
 - data/lib/rodauth/features/oauth_token_revocation.rb +46 -33
 - data/lib/rodauth/features/oidc.rb +382 -241
 - data/lib/rodauth/features/oidc_dynamic_client_registration.rb +127 -51
 - data/lib/rodauth/features/oidc_rp_initiated_logout.rb +115 -0
 - data/lib/rodauth/oauth/database_extensions.rb +8 -6
 - data/lib/rodauth/oauth/http_extensions.rb +74 -0
 - data/lib/rodauth/oauth/railtie.rb +20 -0
 - data/lib/rodauth/oauth/ttl_store.rb +2 -0
 - data/lib/rodauth/oauth/version.rb +1 -1
 - data/lib/rodauth/oauth.rb +29 -1
 - data/locales/en.yml +34 -22
 - data/locales/pt.yml +34 -22
 - data/templates/authorize.str +19 -17
 - data/templates/device_search.str +1 -1
 - data/templates/device_verification.str +2 -2
 - data/templates/jwks_field.str +1 -0
 - data/templates/new_oauth_application.str +1 -2
 - data/templates/oauth_application.str +2 -2
 - data/templates/oauth_application_oauth_grants.str +54 -0
 - data/templates/oauth_applications.str +2 -2
 - data/templates/oauth_grants.str +52 -0
 - metadata +23 -16
 - data/lib/generators/rodauth/oauth/templates/app/models/oauth_token.rb +0 -4
 - data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_tokens.html.erb +0 -39
 - data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_tokens.html.erb +0 -35
 - data/lib/rodauth/features/oauth.rb +0 -9
 - data/lib/rodauth/features/oauth_http_mac.rb +0 -86
 - data/lib/rodauth/features/oauth_token_management.rb +0 -81
 - data/lib/rodauth/oauth/refinements.rb +0 -48
 - data/templates/jwt_public_key_field.str +0 -4
 - data/templates/oauth_application_oauth_tokens.str +0 -52
 - data/templates/oauth_tokens.str +0 -50
 
| 
         @@ -1,33 +1,35 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
      
 3 
     | 
    
         
            +
            require "rodauth/oauth"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
       3 
5 
     | 
    
         
             
            module Rodauth
         
     | 
| 
       4 
6 
     | 
    
         
             
              Feature.define(:oauth_client_credentials_grant, :OauthClientCredentialsGrant) do
         
     | 
| 
       5 
7 
     | 
    
         
             
                depends :oauth_base
         
     | 
| 
       6 
8 
     | 
    
         | 
| 
       7 
     | 
    
         
            -
                 
     | 
| 
      
 9 
     | 
    
         
            +
                def oauth_grant_types_supported
         
     | 
| 
      
 10 
     | 
    
         
            +
                  super | %w[client_credentials]
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
       8 
12 
     | 
    
         | 
| 
       9 
13 
     | 
    
         
             
                private
         
     | 
| 
       10 
14 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
                def  
     | 
| 
       12 
     | 
    
         
            -
                  return super unless grant_type  
     | 
| 
      
 15 
     | 
    
         
            +
                def create_token(grant_type)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  return super unless supported_grant_type?(grant_type, "client_credentials")
         
     | 
| 
       13 
17 
     | 
    
         | 
| 
       14 
     | 
    
         
            -
                   
     | 
| 
       15 
     | 
    
         
            -
                    oauth_tokens_oauth_application_id_column => oauth_application[oauth_applications_id_column],
         
     | 
| 
       16 
     | 
    
         
            -
                    oauth_tokens_scopes_column => scopes.join(oauth_scope_separator)
         
     | 
| 
       17 
     | 
    
         
            -
                  }
         
     | 
| 
       18 
     | 
    
         
            -
                  generate_oauth_token(create_params, false)
         
     | 
| 
       19 
     | 
    
         
            -
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
                  grant_scopes = scopes
         
     | 
| 
       20 
19 
     | 
    
         | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
       22 
     | 
    
         
            -
             
     | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
             
     | 
| 
       25 
     | 
    
         
            -
             
     | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
       27 
     | 
    
         
            -
                def check_valid_response_type?
         
     | 
| 
       28 
     | 
    
         
            -
                  return true if use_oauth_implicit_grant_type? && param_or_nil("response_type") == "token"
         
     | 
| 
      
 20 
     | 
    
         
            +
                  grant_scopes = if grant_scopes
         
     | 
| 
      
 21 
     | 
    
         
            +
                                   redirect_response_error("invalid_scope") unless check_valid_scopes?
         
     | 
| 
      
 22 
     | 
    
         
            +
                                   grant_scopes.join(oauth_scope_separator)
         
     | 
| 
      
 23 
     | 
    
         
            +
                                 else
         
     | 
| 
      
 24 
     | 
    
         
            +
                                   oauth_application[oauth_applications_scopes_column]
         
     | 
| 
      
 25 
     | 
    
         
            +
                                 end
         
     | 
| 
       29 
26 
     | 
    
         | 
| 
       30 
     | 
    
         
            -
                   
     | 
| 
      
 27 
     | 
    
         
            +
                  grant_params = {
         
     | 
| 
      
 28 
     | 
    
         
            +
                    oauth_grants_type_column => "client_credentials",
         
     | 
| 
      
 29 
     | 
    
         
            +
                    oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
         
     | 
| 
      
 30 
     | 
    
         
            +
                    oauth_grants_scopes_column => grant_scopes
         
     | 
| 
      
 31 
     | 
    
         
            +
                  }
         
     | 
| 
      
 32 
     | 
    
         
            +
                  generate_token(grant_params, false)
         
     | 
| 
       31 
33 
     | 
    
         
             
                end
         
     | 
| 
       32 
34 
     | 
    
         
             
              end
         
     | 
| 
       33 
35 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1,11 +1,11 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
      
 3 
     | 
    
         
            +
            require "rodauth/oauth"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
       3 
5 
     | 
    
         
             
            module Rodauth
         
     | 
| 
       4 
     | 
    
         
            -
              Feature.define(: 
     | 
| 
      
 6 
     | 
    
         
            +
              Feature.define(:oauth_device_code_grant, :OauthDeviceCodeGrant) do
         
     | 
| 
       5 
7 
     | 
    
         
             
                depends :oauth_authorize_base
         
     | 
| 
       6 
8 
     | 
    
         | 
| 
       7 
     | 
    
         
            -
                auth_value_method :use_oauth_device_code_grant_type?, false
         
     | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
       9 
9 
     | 
    
         
             
                before "device_authorization"
         
     | 
| 
       10 
10 
     | 
    
         
             
                before "device_verification"
         
     | 
| 
       11 
11 
     | 
    
         | 
| 
         @@ -21,10 +21,12 @@ module Rodauth 
     | 
|
| 
       21 
21 
     | 
    
         
             
                auth_value_method :oauth_grants_user_code_column, :user_code
         
     | 
| 
       22 
22 
     | 
    
         
             
                auth_value_method :oauth_grants_last_polled_at_column, :last_polled_at
         
     | 
| 
       23 
23 
     | 
    
         | 
| 
       24 
     | 
    
         
            -
                translatable_method : 
     | 
| 
       25 
     | 
    
         
            -
                translatable_method : 
     | 
| 
       26 
     | 
    
         
            -
                translatable_method : 
     | 
| 
       27 
     | 
    
         
            -
                translatable_method : 
     | 
| 
      
 24 
     | 
    
         
            +
                translatable_method :oauth_device_search_page_lead, "Insert the user code from the device you'd like to authorize."
         
     | 
| 
      
 25 
     | 
    
         
            +
                translatable_method :oauth_device_verification_page_lead, "The device with user code %<user_code>s would like to access your data."
         
     | 
| 
      
 26 
     | 
    
         
            +
                translatable_method :oauth_expired_token_message, "the device code has expired"
         
     | 
| 
      
 27 
     | 
    
         
            +
                translatable_method :oauth_access_denied_message, "the authorization request has been denied"
         
     | 
| 
      
 28 
     | 
    
         
            +
                translatable_method :oauth_authorization_pending_message, "the authorization request is still pending"
         
     | 
| 
      
 29 
     | 
    
         
            +
                translatable_method :oauth_slow_down_message, "authorization request is still pending but poll interval should be increased"
         
     | 
| 
       28 
30 
     | 
    
         | 
| 
       29 
31 
     | 
    
         
             
                auth_value_method :oauth_device_code_grant_polling_interval, 5 # seconds
         
     | 
| 
       30 
32 
     | 
    
         
             
                auth_value_method :oauth_device_code_grant_user_code_size, 8 # characters
         
     | 
| 
         @@ -38,18 +40,18 @@ module Rodauth 
     | 
|
| 
       38 
40 
     | 
    
         
             
                )
         
     | 
| 
       39 
41 
     | 
    
         | 
| 
       40 
42 
     | 
    
         
             
                # /device-authorization
         
     | 
| 
       41 
     | 
    
         
            -
                 
     | 
| 
       42 
     | 
    
         
            -
                   
     | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
      
 43 
     | 
    
         
            +
                auth_server_route(:device_authorization) do |r|
         
     | 
| 
      
 44 
     | 
    
         
            +
                  require_oauth_application
         
     | 
| 
       44 
45 
     | 
    
         
             
                  before_device_authorization_route
         
     | 
| 
       45 
46 
     | 
    
         | 
| 
       46 
47 
     | 
    
         
             
                  r.post do
         
     | 
| 
       47 
     | 
    
         
            -
                    require_oauth_application
         
     | 
| 
       48 
     | 
    
         
            -
             
     | 
| 
       49 
48 
     | 
    
         
             
                    user_code = generate_user_code
         
     | 
| 
       50 
49 
     | 
    
         
             
                    device_code = transaction do
         
     | 
| 
       51 
50 
     | 
    
         
             
                      before_device_authorization
         
     | 
| 
       52 
     | 
    
         
            -
                      create_oauth_grant( 
     | 
| 
      
 51 
     | 
    
         
            +
                      create_oauth_grant(
         
     | 
| 
      
 52 
     | 
    
         
            +
                        oauth_grants_type_column => "device_code",
         
     | 
| 
      
 53 
     | 
    
         
            +
                        oauth_grants_user_code_column => user_code
         
     | 
| 
      
 54 
     | 
    
         
            +
                      )
         
     | 
| 
       53 
55 
     | 
    
         
             
                    end
         
     | 
| 
       54 
56 
     | 
    
         | 
| 
       55 
57 
     | 
    
         
             
                    json_response_success \
         
     | 
| 
         @@ -63,18 +65,13 @@ module Rodauth 
     | 
|
| 
       63 
65 
     | 
    
         
             
                end
         
     | 
| 
       64 
66 
     | 
    
         | 
| 
       65 
67 
     | 
    
         
             
                # /device
         
     | 
| 
       66 
     | 
    
         
            -
                 
     | 
| 
       67 
     | 
    
         
            -
                  next unless is_authorization_server? && use_oauth_device_code_grant_type?
         
     | 
| 
       68 
     | 
    
         
            -
             
     | 
| 
       69 
     | 
    
         
            -
                  before_device_route
         
     | 
| 
      
 68 
     | 
    
         
            +
                auth_server_route(:device) do |r|
         
     | 
| 
       70 
69 
     | 
    
         
             
                  require_authorizable_account
         
     | 
| 
      
 70 
     | 
    
         
            +
                  before_device_route
         
     | 
| 
       71 
71 
     | 
    
         | 
| 
       72 
72 
     | 
    
         
             
                  r.get do
         
     | 
| 
       73 
73 
     | 
    
         
             
                    if (user_code = param_or_nil("user_code"))
         
     | 
| 
       74 
     | 
    
         
            -
                      oauth_grant =  
     | 
| 
       75 
     | 
    
         
            -
                        oauth_grants_user_code_column => user_code,
         
     | 
| 
       76 
     | 
    
         
            -
                        oauth_grants_revoked_at_column => nil
         
     | 
| 
       77 
     | 
    
         
            -
                      ).where(Sequel[oauth_grants_expires_in_column] >= Sequel::CURRENT_TIMESTAMP).first
         
     | 
| 
      
 74 
     | 
    
         
            +
                      oauth_grant = valid_oauth_grant_ds(oauth_grants_user_code_column => user_code).first
         
     | 
| 
       78 
75 
     | 
    
         | 
| 
       79 
76 
     | 
    
         
             
                      unless oauth_grant
         
     | 
| 
       80 
77 
     | 
    
         
             
                        set_redirect_error_flash user_code_not_found_error_flash
         
     | 
| 
         @@ -90,14 +87,14 @@ module Rodauth 
     | 
|
| 
       90 
87 
     | 
    
         | 
| 
       91 
88 
     | 
    
         
             
                  r.post do
         
     | 
| 
       92 
89 
     | 
    
         
             
                    catch_error do
         
     | 
| 
       93 
     | 
    
         
            -
                      unless param_or_nil("user_code")
         
     | 
| 
       94 
     | 
    
         
            -
                        set_redirect_error_flash  
     | 
| 
      
 90 
     | 
    
         
            +
                      unless (user_code = param_or_nil("user_code")) && !user_code.empty?
         
     | 
| 
      
 91 
     | 
    
         
            +
                        set_redirect_error_flash oauth_invalid_grant_message
         
     | 
| 
       95 
92 
     | 
    
         
             
                        redirect device_path
         
     | 
| 
       96 
93 
     | 
    
         
             
                      end
         
     | 
| 
       97 
94 
     | 
    
         | 
| 
       98 
95 
     | 
    
         
             
                      transaction do
         
     | 
| 
       99 
96 
     | 
    
         
             
                        before_device_verification
         
     | 
| 
       100 
     | 
    
         
            -
                         
     | 
| 
      
 97 
     | 
    
         
            +
                        create_token("device_code")
         
     | 
| 
       101 
98 
     | 
    
         
             
                      end
         
     | 
| 
       102 
99 
     | 
    
         
             
                    end
         
     | 
| 
       103 
100 
     | 
    
         
             
                    set_notice_flash device_verification_notice_flash
         
     | 
| 
         @@ -114,6 +111,10 @@ module Rodauth 
     | 
|
| 
       114 
111 
     | 
    
         
             
                  end
         
     | 
| 
       115 
112 
     | 
    
         
             
                end
         
     | 
| 
       116 
113 
     | 
    
         | 
| 
      
 114 
     | 
    
         
            +
                def oauth_grant_types_supported
         
     | 
| 
      
 115 
     | 
    
         
            +
                  super | %w[urn:ietf:params:oauth:grant-type:device_code]
         
     | 
| 
      
 116 
     | 
    
         
            +
                end
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
       117 
118 
     | 
    
         
             
                private
         
     | 
| 
       118 
119 
     | 
    
         | 
| 
       119 
120 
     | 
    
         
             
                def generate_user_code
         
     | 
| 
         @@ -124,82 +125,61 @@ module Rodauth 
     | 
|
| 
       124 
125 
     | 
    
         
             
                              .rjust(user_code_size, "0")
         
     | 
| 
       125 
126 
     | 
    
         
             
                end
         
     | 
| 
       126 
127 
     | 
    
         | 
| 
       127 
     | 
    
         
            -
                 
     | 
| 
       128 
     | 
    
         
            -
             
     | 
| 
       129 
     | 
    
         
            -
                   
     | 
| 
       130 
     | 
    
         
            -
                  # requests may be performed by devices with no knowledge of client secret.
         
     | 
| 
       131 
     | 
    
         
            -
                  return true if !client_secret && use_oauth_device_code_grant_type?
         
     | 
| 
      
 128 
     | 
    
         
            +
                # TODO: think about removing this and recommend PKCE
         
     | 
| 
      
 129 
     | 
    
         
            +
                def supports_auth_method?(oauth_application, auth_method)
         
     | 
| 
      
 130 
     | 
    
         
            +
                  return super unless auth_method == "none"
         
     | 
| 
       132 
131 
     | 
    
         | 
| 
       133 
     | 
    
         
            -
                  super
         
     | 
| 
      
 132 
     | 
    
         
            +
                  request.path == device_authorization_path || request.params.key?("device_code") || super
         
     | 
| 
       134 
133 
     | 
    
         
             
                end
         
     | 
| 
       135 
134 
     | 
    
         | 
| 
       136 
     | 
    
         
            -
                def  
     | 
| 
      
 135 
     | 
    
         
            +
                def create_token(grant_type)
         
     | 
| 
       137 
136 
     | 
    
         
             
                  if supported_grant_type?(grant_type, "urn:ietf:params:oauth:grant-type:device_code")
         
     | 
| 
       138 
     | 
    
         
            -
                    throw_json_response_error(invalid_oauth_response_status, "invalid_grant_type") unless use_oauth_device_code_grant_type?
         
     | 
| 
       139 
137 
     | 
    
         | 
| 
       140 
138 
     | 
    
         
             
                    oauth_grant = db[oauth_grants_table].where(
         
     | 
| 
      
 139 
     | 
    
         
            +
                      oauth_grants_type_column => "device_code",
         
     | 
| 
       141 
140 
     | 
    
         
             
                      oauth_grants_code_column => param("device_code"),
         
     | 
| 
       142 
141 
     | 
    
         
             
                      oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column]
         
     | 
| 
       143 
142 
     | 
    
         
             
                    ).for_update.first
         
     | 
| 
       144 
143 
     | 
    
         | 
| 
       145 
     | 
    
         
            -
                    throw_json_response_error( 
     | 
| 
      
 144 
     | 
    
         
            +
                    throw_json_response_error(oauth_invalid_response_status, "invalid_grant") unless oauth_grant
         
     | 
| 
       146 
145 
     | 
    
         | 
| 
       147 
146 
     | 
    
         
             
                    now = Time.now
         
     | 
| 
       148 
147 
     | 
    
         | 
| 
       149 
     | 
    
         
            -
                    if oauth_grant[ 
     | 
| 
       150 
     | 
    
         
            -
                       
     | 
| 
       151 
     | 
    
         
            -
             
     | 
| 
       152 
     | 
    
         
            -
             
     | 
| 
       153 
     | 
    
         
            -
             
     | 
| 
       154 
     | 
    
         
            -
             
     | 
| 
      
 148 
     | 
    
         
            +
                    if oauth_grant[oauth_grants_user_code_column].nil?
         
     | 
| 
      
 149 
     | 
    
         
            +
                      return create_token_from_authorization_code(
         
     | 
| 
      
 150 
     | 
    
         
            +
                        { oauth_grants_id_column => oauth_grant[oauth_grants_id_column] },
         
     | 
| 
      
 151 
     | 
    
         
            +
                        oauth_grant: oauth_grant
         
     | 
| 
      
 152 
     | 
    
         
            +
                      )
         
     | 
| 
      
 153 
     | 
    
         
            +
                    end
         
     | 
| 
       155 
154 
     | 
    
         | 
| 
       156 
     | 
    
         
            -
             
     | 
| 
      
 155 
     | 
    
         
            +
                    if oauth_grant[oauth_grants_revoked_at_column]
         
     | 
| 
      
 156 
     | 
    
         
            +
                      throw_json_response_error(oauth_invalid_response_status, "access_denied")
         
     | 
| 
       157 
157 
     | 
    
         
             
                    elsif oauth_grant[oauth_grants_expires_in_column] < now
         
     | 
| 
       158 
     | 
    
         
            -
                      throw_json_response_error( 
     | 
| 
      
 158 
     | 
    
         
            +
                      throw_json_response_error(oauth_invalid_response_status, "expired_token")
         
     | 
| 
       159 
159 
     | 
    
         
             
                    else
         
     | 
| 
       160 
160 
     | 
    
         
             
                      last_polled_at = oauth_grant[oauth_grants_last_polled_at_column]
         
     | 
| 
       161 
161 
     | 
    
         
             
                      if last_polled_at && convert_timestamp(last_polled_at) + oauth_device_code_grant_polling_interval > now
         
     | 
| 
       162 
     | 
    
         
            -
                        throw_json_response_error( 
     | 
| 
      
 162 
     | 
    
         
            +
                        throw_json_response_error(oauth_invalid_response_status, "slow_down")
         
     | 
| 
       163 
163 
     | 
    
         
             
                      else
         
     | 
| 
       164 
164 
     | 
    
         
             
                        db[oauth_grants_table].where(oauth_grants_id_column => oauth_grant[oauth_grants_id_column])
         
     | 
| 
       165 
165 
     | 
    
         
             
                                              .update(oauth_grants_last_polled_at_column => Sequel::CURRENT_TIMESTAMP)
         
     | 
| 
       166 
     | 
    
         
            -
                        throw_json_response_error( 
     | 
| 
      
 166 
     | 
    
         
            +
                        throw_json_response_error(oauth_invalid_response_status, "authorization_pending")
         
     | 
| 
       167 
167 
     | 
    
         
             
                      end
         
     | 
| 
       168 
168 
     | 
    
         
             
                    end
         
     | 
| 
       169 
     | 
    
         
            -
                    oauth_token
         
     | 
| 
       170 
169 
     | 
    
         
             
                  elsif grant_type == "device_code"
         
     | 
| 
       171 
     | 
    
         
            -
                    redirect_response_error("invalid_grant_type") unless use_oauth_device_code_grant_type?
         
     | 
| 
       172 
170 
     | 
    
         | 
| 
       173 
171 
     | 
    
         
             
                    # fetch oauth grant
         
     | 
| 
       174 
     | 
    
         
            -
                     
     | 
| 
       175 
     | 
    
         
            -
                      oauth_grants_user_code_column => param("user_code") 
     | 
| 
       176 
     | 
    
         
            -
             
     | 
| 
       177 
     | 
    
         
            -
             
     | 
| 
       178 
     | 
    
         
            -
             
     | 
| 
       179 
     | 
    
         
            -
                                                        .first
         
     | 
| 
       180 
     | 
    
         
            -
             
     | 
| 
       181 
     | 
    
         
            -
                    return unless oauth_grant
         
     | 
| 
       182 
     | 
    
         
            -
             
     | 
| 
       183 
     | 
    
         
            -
                    # update ownership
         
     | 
| 
       184 
     | 
    
         
            -
                    db[oauth_grants_table].where(oauth_grants_id_column => oauth_grant[oauth_grants_id_column])
         
     | 
| 
       185 
     | 
    
         
            -
                                          .update(
         
     | 
| 
       186 
     | 
    
         
            -
                                            oauth_grants_user_code_column => nil,
         
     | 
| 
       187 
     | 
    
         
            -
                                            oauth_grants_account_id_column => account_id
         
     | 
| 
       188 
     | 
    
         
            -
                                          )
         
     | 
| 
       189 
     | 
    
         
            -
             
     | 
| 
       190 
     | 
    
         
            -
                    create_params = {
         
     | 
| 
       191 
     | 
    
         
            -
                      oauth_tokens_account_id_column => account_id,
         
     | 
| 
       192 
     | 
    
         
            -
                      oauth_tokens_oauth_application_id_column => oauth_grant[oauth_grants_oauth_application_id_column],
         
     | 
| 
       193 
     | 
    
         
            -
                      oauth_tokens_oauth_grant_id_column => oauth_grant[oauth_grants_id_column],
         
     | 
| 
       194 
     | 
    
         
            -
                      oauth_tokens_scopes_column => oauth_grant[oauth_grants_scopes_column]
         
     | 
| 
       195 
     | 
    
         
            -
                    }
         
     | 
| 
       196 
     | 
    
         
            -
                    create_oauth_token_from_authorization_code(oauth_grant, create_params)
         
     | 
| 
      
 172 
     | 
    
         
            +
                    rs = valid_oauth_grant_ds(
         
     | 
| 
      
 173 
     | 
    
         
            +
                      oauth_grants_user_code_column => param("user_code")
         
     | 
| 
      
 174 
     | 
    
         
            +
                    ).update(oauth_grants_user_code_column => nil, oauth_grants_type_column => "device_code")
         
     | 
| 
      
 175 
     | 
    
         
            +
             
     | 
| 
      
 176 
     | 
    
         
            +
                    return unless rs.positive?
         
     | 
| 
       197 
177 
     | 
    
         
             
                  else
         
     | 
| 
       198 
178 
     | 
    
         
             
                    super
         
     | 
| 
       199 
179 
     | 
    
         
             
                  end
         
     | 
| 
       200 
180 
     | 
    
         
             
                end
         
     | 
| 
       201 
181 
     | 
    
         | 
| 
       202 
     | 
    
         
            -
                def  
     | 
| 
      
 182 
     | 
    
         
            +
                def validate_token_params
         
     | 
| 
       203 
183 
     | 
    
         
             
                  grant_type = param_or_nil("grant_type")
         
     | 
| 
       204 
184 
     | 
    
         | 
| 
       205 
185 
     | 
    
         
             
                  if grant_type == "urn:ietf:params:oauth:grant-type:device_code" && !param_or_nil("device_code")
         
     | 
| 
         @@ -208,12 +188,21 @@ module Rodauth 
     | 
|
| 
       208 
188 
     | 
    
         
             
                  super
         
     | 
| 
       209 
189 
     | 
    
         
             
                end
         
     | 
| 
       210 
190 
     | 
    
         | 
| 
      
 191 
     | 
    
         
            +
                def store_token(grant_params, update_params = {})
         
     | 
| 
      
 192 
     | 
    
         
            +
                  return super unless grant_params[oauth_grants_user_code_column]
         
     | 
| 
      
 193 
     | 
    
         
            +
             
     | 
| 
      
 194 
     | 
    
         
            +
                  # do not clean up device code just yet
         
     | 
| 
      
 195 
     | 
    
         
            +
                  update_params.delete(oauth_grants_code_column)
         
     | 
| 
      
 196 
     | 
    
         
            +
             
     | 
| 
      
 197 
     | 
    
         
            +
                  update_params[oauth_grants_user_code_column] = nil
         
     | 
| 
      
 198 
     | 
    
         
            +
                  update_params[oauth_grants_account_id_column] = account_id
         
     | 
| 
      
 199 
     | 
    
         
            +
             
     | 
| 
      
 200 
     | 
    
         
            +
                  super(grant_params, update_params)
         
     | 
| 
      
 201 
     | 
    
         
            +
                end
         
     | 
| 
      
 202 
     | 
    
         
            +
             
     | 
| 
       211 
203 
     | 
    
         
             
                def oauth_server_metadata_body(*)
         
     | 
| 
       212 
204 
     | 
    
         
             
                  super.tap do |data|
         
     | 
| 
       213 
     | 
    
         
            -
                     
     | 
| 
       214 
     | 
    
         
            -
                      data[:grant_types_supported] << "urn:ietf:params:oauth:grant-type:device_code"
         
     | 
| 
       215 
     | 
    
         
            -
                      data[:device_authorization_endpoint] = device_authorization_url
         
     | 
| 
       216 
     | 
    
         
            -
                    end
         
     | 
| 
      
 205 
     | 
    
         
            +
                    data[:device_authorization_endpoint] = device_authorization_url
         
     | 
| 
       217 
206 
     | 
    
         
             
                  end
         
     | 
| 
       218 
207 
     | 
    
         
             
                end
         
     | 
| 
       219 
208 
     | 
    
         
             
              end
         
     | 
| 
         @@ -1,19 +1,19 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
      
 3 
     | 
    
         
            +
            require "rodauth/oauth"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
       3 
5 
     | 
    
         
             
            module Rodauth
         
     | 
| 
       4 
6 
     | 
    
         
             
              Feature.define(:oauth_dynamic_client_registration, :OauthDynamicClientRegistration) do
         
     | 
| 
       5 
7 
     | 
    
         
             
                depends :oauth_base
         
     | 
| 
       6 
8 
     | 
    
         | 
| 
       7 
9 
     | 
    
         
             
                before "register"
         
     | 
| 
       8 
10 
     | 
    
         | 
| 
       9 
     | 
    
         
            -
                auth_value_method :oauth_client_registration_required_params, %w[redirect_uris client_name 
     | 
| 
      
 11 
     | 
    
         
            +
                auth_value_method :oauth_client_registration_required_params, %w[redirect_uris client_name]
         
     | 
| 
       10 
12 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
                PROTECTED_APPLICATION_ATTRIBUTES = % 
     | 
| 
      
 13 
     | 
    
         
            +
                PROTECTED_APPLICATION_ATTRIBUTES = %w[account_id client_id].freeze
         
     | 
| 
       12 
14 
     | 
    
         | 
| 
       13 
15 
     | 
    
         
             
                # /register
         
     | 
| 
       14 
     | 
    
         
            -
                 
     | 
| 
       15 
     | 
    
         
            -
                  next unless is_authorization_server?
         
     | 
| 
       16 
     | 
    
         
            -
             
     | 
| 
      
 16 
     | 
    
         
            +
                auth_server_route(:register) do |r|
         
     | 
| 
       17 
17 
     | 
    
         
             
                  before_register_route
         
     | 
| 
       18 
18 
     | 
    
         | 
| 
       19 
19 
     | 
    
         
             
                  validate_client_registration_params
         
     | 
| 
         @@ -43,8 +43,17 @@ module Rodauth 
     | 
|
| 
       43 
43 
     | 
    
         | 
| 
       44 
44 
     | 
    
         
             
                private
         
     | 
| 
       45 
45 
     | 
    
         | 
| 
       46 
     | 
    
         
            -
                def  
     | 
| 
       47 
     | 
    
         
            -
                   
     | 
| 
      
 46 
     | 
    
         
            +
                def _before_register
         
     | 
| 
      
 47 
     | 
    
         
            +
                  raise %{dynamic client registration requires authentication.
         
     | 
| 
      
 48 
     | 
    
         
            +
                    Override ´before_register` to perform it.
         
     | 
| 
      
 49 
     | 
    
         
            +
                    example:
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                      before_register do
         
     | 
| 
      
 52 
     | 
    
         
            +
                        account = _account_from_login(request.env["HTTP_X_USER_EMAIL"])
         
     | 
| 
      
 53 
     | 
    
         
            +
                        authorization_required unless account
         
     | 
| 
      
 54 
     | 
    
         
            +
                        @oauth_application_params[:account_id] = account[:id]
         
     | 
| 
      
 55 
     | 
    
         
            +
                      end
         
     | 
| 
      
 56 
     | 
    
         
            +
                  }
         
     | 
| 
       48 
57 
     | 
    
         
             
                end
         
     | 
| 
       49 
58 
     | 
    
         | 
| 
       50 
59 
     | 
    
         
             
                def validate_client_registration_params
         
     | 
| 
         @@ -53,41 +62,43 @@ module Rodauth 
     | 
|
| 
       53 
62 
     | 
    
         
             
                      register_throw_json_response_error("invalid_client_metadata", register_required_param_message(required_param))
         
     | 
| 
       54 
63 
     | 
    
         
             
                    end
         
     | 
| 
       55 
64 
     | 
    
         
             
                  end
         
     | 
| 
       56 
     | 
    
         
            -
                  metadata = registration_metadata
         
     | 
| 
       57 
65 
     | 
    
         | 
| 
       58 
66 
     | 
    
         
             
                  @oauth_application_params = request.params.each_with_object({}) do |(key, value), params|
         
     | 
| 
       59 
67 
     | 
    
         
             
                    case key
         
     | 
| 
       60 
68 
     | 
    
         
             
                    when "redirect_uris"
         
     | 
| 
       61 
69 
     | 
    
         
             
                      if value.is_a?(Array)
         
     | 
| 
       62 
70 
     | 
    
         
             
                        value = value.each do |uri|
         
     | 
| 
       63 
     | 
    
         
            -
                           
     | 
| 
      
 71 
     | 
    
         
            +
                          unless check_valid_no_fragment_uri?(uri)
         
     | 
| 
      
 72 
     | 
    
         
            +
                            register_throw_json_response_error("invalid_redirect_uri",
         
     | 
| 
      
 73 
     | 
    
         
            +
                                                               register_invalid_uri_message(uri))
         
     | 
| 
      
 74 
     | 
    
         
            +
                          end
         
     | 
| 
       64 
75 
     | 
    
         
             
                        end.join(" ")
         
     | 
| 
       65 
76 
     | 
    
         
             
                      else
         
     | 
| 
       66 
77 
     | 
    
         
             
                        register_throw_json_response_error("invalid_redirect_uri", register_invalid_uri_message(value))
         
     | 
| 
       67 
78 
     | 
    
         
             
                      end
         
     | 
| 
       68 
79 
     | 
    
         
             
                      key = oauth_applications_redirect_uri_column
         
     | 
| 
       69 
80 
     | 
    
         
             
                    when "token_endpoint_auth_method"
         
     | 
| 
       70 
     | 
    
         
            -
                      unless  
     | 
| 
       71 
     | 
    
         
            -
                        register_throw_json_response_error("invalid_client_metadata",  
     | 
| 
      
 81 
     | 
    
         
            +
                      unless oauth_token_endpoint_auth_methods_supported.include?(value)
         
     | 
| 
      
 82 
     | 
    
         
            +
                        register_throw_json_response_error("invalid_client_metadata", register_invalid_client_metadata_message(key, value))
         
     | 
| 
       72 
83 
     | 
    
         
             
                      end
         
     | 
| 
       73 
84 
     | 
    
         
             
                      # verify if in range
         
     | 
| 
       74 
85 
     | 
    
         
             
                      key = oauth_applications_token_endpoint_auth_method_column
         
     | 
| 
       75 
86 
     | 
    
         
             
                    when "grant_types"
         
     | 
| 
       76 
87 
     | 
    
         
             
                      if value.is_a?(Array)
         
     | 
| 
       77 
88 
     | 
    
         
             
                        value = value.each do |grant_type|
         
     | 
| 
       78 
     | 
    
         
            -
                          unless  
     | 
| 
       79 
     | 
    
         
            -
                            register_throw_json_response_error("invalid_client_metadata",  
     | 
| 
      
 89 
     | 
    
         
            +
                          unless oauth_grant_types_supported.include?(grant_type)
         
     | 
| 
      
 90 
     | 
    
         
            +
                            register_throw_json_response_error("invalid_client_metadata", register_invalid_client_metadata_message(grant_type, value))
         
     | 
| 
       80 
91 
     | 
    
         
             
                          end
         
     | 
| 
       81 
92 
     | 
    
         
             
                        end.join(" ")
         
     | 
| 
       82 
93 
     | 
    
         
             
                      else
         
     | 
| 
       83 
     | 
    
         
            -
                         
     | 
| 
      
 94 
     | 
    
         
            +
                        register_throw_json_response_error("invalid_client_metadata", register_invalid_client_metadata_message(key, value))
         
     | 
| 
       84 
95 
     | 
    
         
             
                      end
         
     | 
| 
       85 
96 
     | 
    
         
             
                      key = oauth_applications_grant_types_column
         
     | 
| 
       86 
97 
     | 
    
         
             
                    when "response_types"
         
     | 
| 
       87 
98 
     | 
    
         
             
                      if value.is_a?(Array)
         
     | 
| 
       88 
     | 
    
         
            -
                        grant_types = request.params["grant_types"] ||  
     | 
| 
      
 99 
     | 
    
         
            +
                        grant_types = request.params["grant_types"] || oauth_grant_types_supported
         
     | 
| 
       89 
100 
     | 
    
         
             
                        value = value.each do |response_type|
         
     | 
| 
       90 
     | 
    
         
            -
                          unless  
     | 
| 
      
 101 
     | 
    
         
            +
                          unless oauth_response_types_supported.include?(response_type)
         
     | 
| 
       91 
102 
     | 
    
         
             
                            register_throw_json_response_error("invalid_client_metadata",
         
     | 
| 
       92 
103 
     | 
    
         
             
                                                               register_invalid_response_type_message(response_type))
         
     | 
| 
       93 
104 
     | 
    
         
             
                          end
         
     | 
| 
         @@ -95,7 +106,7 @@ module Rodauth 
     | 
|
| 
       95 
106 
     | 
    
         
             
                          validate_client_registration_response_type(response_type, grant_types)
         
     | 
| 
       96 
107 
     | 
    
         
             
                        end.join(" ")
         
     | 
| 
       97 
108 
     | 
    
         
             
                      else
         
     | 
| 
       98 
     | 
    
         
            -
                         
     | 
| 
      
 109 
     | 
    
         
            +
                        register_throw_json_response_error("invalid_client_metadata", register_invalid_client_metadata_message(key, value))
         
     | 
| 
       99 
110 
     | 
    
         
             
                      end
         
     | 
| 
       100 
111 
     | 
    
         
             
                      key = oauth_applications_response_types_column
         
     | 
| 
       101 
112 
     | 
    
         
             
                      # verify if in range and match grant type
         
     | 
| 
         @@ -133,10 +144,10 @@ module Rodauth 
     | 
|
| 
       133 
144 
     | 
    
         
             
                      key = oauth_applications_name_column
         
     | 
| 
       134 
145 
     | 
    
         
             
                    else
         
     | 
| 
       135 
146 
     | 
    
         
             
                      if respond_to?(:"oauth_applications_#{key}_column")
         
     | 
| 
       136 
     | 
    
         
            -
                         
     | 
| 
       137 
     | 
    
         
            -
                        if PROTECTED_APPLICATION_ATTRIBUTES.include?(property)
         
     | 
| 
      
 147 
     | 
    
         
            +
                        if PROTECTED_APPLICATION_ATTRIBUTES.include?(key)
         
     | 
| 
       138 
148 
     | 
    
         
             
                          register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(key))
         
     | 
| 
       139 
149 
     | 
    
         
             
                        end
         
     | 
| 
      
 150 
     | 
    
         
            +
                        property = :"oauth_applications_#{key}_column"
         
     | 
| 
       140 
151 
     | 
    
         
             
                        key = __send__(property)
         
     | 
| 
       141 
152 
     | 
    
         
             
                      elsif !db[oauth_applications_table].columns.include?(key.to_sym)
         
     | 
| 
       142 
153 
     | 
    
         
             
                        register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(key))
         
     | 
| 
         @@ -167,16 +178,20 @@ module Rodauth 
     | 
|
| 
       167 
178 
     | 
    
         
             
                end
         
     | 
| 
       168 
179 
     | 
    
         | 
| 
       169 
180 
     | 
    
         
             
                def do_register(return_params = request.params.dup)
         
     | 
| 
      
 181 
     | 
    
         
            +
                  applications_ds = db[oauth_applications_table]
         
     | 
| 
      
 182 
     | 
    
         
            +
                  application_columns = applications_ds.columns
         
     | 
| 
      
 183 
     | 
    
         
            +
             
     | 
| 
       170 
184 
     | 
    
         
             
                  # set defaults
         
     | 
| 
       171 
185 
     | 
    
         
             
                  create_params = @oauth_application_params
         
     | 
| 
       172 
     | 
    
         
            -
                  create_params[oauth_applications_scopes_column] ||= return_params["scopes"] =  
     | 
| 
       173 
     | 
    
         
            -
                  create_params[ 
     | 
| 
       174 
     | 
    
         
            -
                    return_params[" 
     | 
| 
       175 
     | 
    
         
            -
                    "client_secret_basic"
         
     | 
| 
       176 
     | 
    
         
            -
                  end
         
     | 
| 
       177 
     | 
    
         
            -
                  create_params[oauth_applications_grant_types_column] ||= begin
         
     | 
| 
       178 
     | 
    
         
            -
                    return_params["grant_types"] = %w[authorization_code]
         
     | 
| 
      
 186 
     | 
    
         
            +
                  create_params[oauth_applications_scopes_column] ||= return_params["scopes"] = oauth_application_scopes.join(" ")
         
     | 
| 
      
 187 
     | 
    
         
            +
                  if create_params[oauth_applications_grant_types_column] ||= begin
         
     | 
| 
      
 188 
     | 
    
         
            +
                    return_params["grant_types"] = %w[authorization_code] # rubocop:disable Lint/AssignmentInCondition
         
     | 
| 
       179 
189 
     | 
    
         
             
                    "authorization_code"
         
     | 
| 
      
 190 
     | 
    
         
            +
                  end
         
     | 
| 
      
 191 
     | 
    
         
            +
                    create_params[oauth_applications_token_endpoint_auth_method_column] ||= begin
         
     | 
| 
      
 192 
     | 
    
         
            +
                      return_params["token_endpoint_auth_method"] = "client_secret_basic"
         
     | 
| 
      
 193 
     | 
    
         
            +
                      "client_secret_basic"
         
     | 
| 
      
 194 
     | 
    
         
            +
                    end
         
     | 
| 
       180 
195 
     | 
    
         
             
                  end
         
     | 
| 
       181 
196 
     | 
    
         
             
                  create_params[oauth_applications_response_types_column] ||= begin
         
     | 
| 
       182 
197 
     | 
    
         
             
                    return_params["response_types"] = %w[code]
         
     | 
| 
         @@ -188,22 +203,24 @@ module Rodauth 
     | 
|
| 
       188 
203 
     | 
    
         
             
                    return_params["client_id"] = client_id
         
     | 
| 
       189 
204 
     | 
    
         
             
                    return_params["client_id_issued_at"] = Time.now.utc.iso8601
         
     | 
| 
       190 
205 
     | 
    
         
             
                    if create_params.key?(oauth_applications_client_secret_column)
         
     | 
| 
       191 
     | 
    
         
            -
                      create_params 
     | 
| 
      
 206 
     | 
    
         
            +
                      set_client_secret(create_params, create_params[oauth_applications_client_secret_column])
         
     | 
| 
       192 
207 
     | 
    
         
             
                      return_params.delete("client_secret")
         
     | 
| 
       193 
208 
     | 
    
         
             
                    else
         
     | 
| 
       194 
209 
     | 
    
         
             
                      client_secret = oauth_unique_id_generator
         
     | 
| 
       195 
     | 
    
         
            -
                      create_params 
     | 
| 
      
 210 
     | 
    
         
            +
                      set_client_secret(create_params, client_secret)
         
     | 
| 
       196 
211 
     | 
    
         
             
                      return_params["client_secret"] = client_secret
         
     | 
| 
       197 
212 
     | 
    
         
             
                      return_params["client_secret_expires_at"] = 0
         
     | 
| 
      
 213 
     | 
    
         
            +
             
     | 
| 
      
 214 
     | 
    
         
            +
                      create_params.delete_if { |k, _| !application_columns.include?(k) }
         
     | 
| 
       198 
215 
     | 
    
         
             
                    end
         
     | 
| 
       199 
     | 
    
         
            -
                     
     | 
| 
      
 216 
     | 
    
         
            +
                    applications_ds.insert(create_params)
         
     | 
| 
       200 
217 
     | 
    
         
             
                  end
         
     | 
| 
       201 
218 
     | 
    
         | 
| 
       202 
219 
     | 
    
         
             
                  return_params
         
     | 
| 
       203 
220 
     | 
    
         
             
                end
         
     | 
| 
       204 
221 
     | 
    
         | 
| 
       205 
222 
     | 
    
         
             
                def register_throw_json_response_error(code, message)
         
     | 
| 
       206 
     | 
    
         
            -
                  throw_json_response_error( 
     | 
| 
      
 223 
     | 
    
         
            +
                  throw_json_response_error(oauth_invalid_response_status, code, message)
         
     | 
| 
       207 
224 
     | 
    
         
             
                end
         
     | 
| 
       208 
225 
     | 
    
         | 
| 
       209 
226 
     | 
    
         
             
                def register_required_param_message(key)
         
     | 
| 
         @@ -214,6 +231,10 @@ module Rodauth 
     | 
|
| 
       214 
231 
     | 
    
         
             
                  "The param '#{key}' is not supported by this server."
         
     | 
| 
       215 
232 
     | 
    
         
             
                end
         
     | 
| 
       216 
233 
     | 
    
         | 
| 
      
 234 
     | 
    
         
            +
                def register_invalid_client_metadata_message(key, value)
         
     | 
| 
      
 235 
     | 
    
         
            +
                  "The value '#{value}' is not supported by this server for param '#{key}'."
         
     | 
| 
      
 236 
     | 
    
         
            +
                end
         
     | 
| 
      
 237 
     | 
    
         
            +
             
     | 
| 
       217 
238 
     | 
    
         
             
                def register_invalid_contacts_message(contacts)
         
     | 
| 
       218 
239 
     | 
    
         
             
                  "The contacts '#{contacts}' are not allowed by this server."
         
     | 
| 
       219 
240 
     | 
    
         
             
                end
         
     | 
| 
         @@ -230,7 +251,7 @@ module Rodauth 
     | 
|
| 
       230 
251 
     | 
    
         
             
                  "The given scopes (#{scopes}) are not allowed by this server."
         
     | 
| 
       231 
252 
     | 
    
         
             
                end
         
     | 
| 
       232 
253 
     | 
    
         | 
| 
       233 
     | 
    
         
            -
                def  
     | 
| 
      
 254 
     | 
    
         
            +
                def register_oauth_invalid_grant_type_message(grant_type)
         
     | 
| 
       234 
255 
     | 
    
         
             
                  "The grant type #{grant_type} is not allowed by this server."
         
     | 
| 
       235 
256 
     | 
    
         
             
                end
         
     | 
| 
       236 
257 
     | 
    
         | 
| 
         @@ -0,0 +1,70 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "rodauth/oauth"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Rodauth
         
     | 
| 
      
 6 
     | 
    
         
            +
              Feature.define(:oauth_grant_management, :OauthTokenManagement) do
         
     | 
| 
      
 7 
     | 
    
         
            +
                depends :oauth_management_base, :oauth_token_revocation
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                view "oauth_grants", "My Oauth Grants", "oauth_grants"
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                button "Revoke", "oauth_grant_revoke"
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                auth_value_method :oauth_grants_path, "oauth-grants"
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                %w[type token refresh_token expires_in revoked_at].each do |param|
         
     | 
| 
      
 16 
     | 
    
         
            +
                  translatable_method :"oauth_grants_#{param}_label", param.gsub("_", " ").capitalize
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
                translatable_method :oauth_no_grants_text, "No oauth grants yet!"
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                auth_value_method :oauth_grants_route, "oauth-grants"
         
     | 
| 
      
 21 
     | 
    
         
            +
                auth_value_method :oauth_grants_id_pattern, Integer
         
     | 
| 
      
 22 
     | 
    
         
            +
                auth_value_method :oauth_grants_per_page, 20
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                auth_value_methods(
         
     | 
| 
      
 25 
     | 
    
         
            +
                  :oauth_grant_path
         
     | 
| 
      
 26 
     | 
    
         
            +
                )
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                def oauth_grants_path(opts = {})
         
     | 
| 
      
 29 
     | 
    
         
            +
                  route_path(oauth_grants_route, opts)
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                def oauth_grant_path(id)
         
     | 
| 
      
 33 
     | 
    
         
            +
                  "#{oauth_grants_path}/#{id}"
         
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                def load_oauth_grant_management_routes
         
     | 
| 
      
 37 
     | 
    
         
            +
                  request.on(oauth_grants_route) do
         
     | 
| 
      
 38 
     | 
    
         
            +
                    check_csrf if check_csrf?
         
     | 
| 
      
 39 
     | 
    
         
            +
                    require_account
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                    request.post(oauth_grants_id_pattern) do |id|
         
     | 
| 
      
 42 
     | 
    
         
            +
                      db[oauth_grants_table]
         
     | 
| 
      
 43 
     | 
    
         
            +
                        .where(oauth_grants_id_column => id)
         
     | 
| 
      
 44 
     | 
    
         
            +
                        .where(oauth_grants_account_id_column => account_id)
         
     | 
| 
      
 45 
     | 
    
         
            +
                        .update(oauth_grants_revoked_at_column => Sequel::CURRENT_TIMESTAMP)
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                      set_notice_flash revoke_oauth_grant_notice_flash
         
     | 
| 
      
 48 
     | 
    
         
            +
                      redirect oauth_grants_path || "/"
         
     | 
| 
      
 49 
     | 
    
         
            +
                    end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                    request.is do
         
     | 
| 
      
 52 
     | 
    
         
            +
                      request.get do
         
     | 
| 
      
 53 
     | 
    
         
            +
                        page = Integer(param_or_nil("page") || 1)
         
     | 
| 
      
 54 
     | 
    
         
            +
                        per_page = per_page_param(oauth_grants_per_page)
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                        scope.instance_variable_set(:@oauth_grants, db[oauth_grants_table]
         
     | 
| 
      
 57 
     | 
    
         
            +
                          .select(Sequel[oauth_grants_table].*, Sequel[oauth_applications_table][oauth_applications_name_column])
         
     | 
| 
      
 58 
     | 
    
         
            +
                          .join(oauth_applications_table, Sequel[oauth_grants_table][oauth_grants_oauth_application_id_column] =>
         
     | 
| 
      
 59 
     | 
    
         
            +
                            Sequel[oauth_applications_table][oauth_applications_id_column])
         
     | 
| 
      
 60 
     | 
    
         
            +
                          .where(Sequel[oauth_grants_table][oauth_grants_account_id_column] => account_id)
         
     | 
| 
      
 61 
     | 
    
         
            +
                          .where(oauth_grants_revoked_at_column => nil)
         
     | 
| 
      
 62 
     | 
    
         
            +
                          .order(Sequel.desc(oauth_grants_id_column))
         
     | 
| 
      
 63 
     | 
    
         
            +
                          .paginate(page, per_page))
         
     | 
| 
      
 64 
     | 
    
         
            +
                        oauth_grants_view
         
     | 
| 
      
 65 
     | 
    
         
            +
                      end
         
     | 
| 
      
 66 
     | 
    
         
            +
                    end
         
     | 
| 
      
 67 
     | 
    
         
            +
                  end
         
     | 
| 
      
 68 
     | 
    
         
            +
                end
         
     | 
| 
      
 69 
     | 
    
         
            +
              end
         
     | 
| 
      
 70 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -1,39 +1,51 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
      
 3 
     | 
    
         
            +
            require "rodauth/oauth"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
       3 
5 
     | 
    
         
             
            module Rodauth
         
     | 
| 
       4 
6 
     | 
    
         
             
              Feature.define(:oauth_implicit_grant, :OauthImplicitGrant) do
         
     | 
| 
       5 
7 
     | 
    
         
             
                depends :oauth_authorize_base
         
     | 
| 
       6 
8 
     | 
    
         | 
| 
       7 
     | 
    
         
            -
                 
     | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
       9 
     | 
    
         
            -
                 
     | 
| 
      
 9 
     | 
    
         
            +
                def oauth_grant_types_supported
         
     | 
| 
      
 10 
     | 
    
         
            +
                  super | %w[implicit]
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
       10 
12 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
                def  
     | 
| 
       12 
     | 
    
         
            -
                   
     | 
| 
      
 13 
     | 
    
         
            +
                def oauth_response_types_supported
         
     | 
| 
      
 14 
     | 
    
         
            +
                  super | %w[token]
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
       13 
16 
     | 
    
         | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
      
 17 
     | 
    
         
            +
                def oauth_response_modes_supported
         
     | 
| 
      
 18 
     | 
    
         
            +
                  super | %w[fragment]
         
     | 
| 
       15 
19 
     | 
    
         
             
                end
         
     | 
| 
       16 
20 
     | 
    
         | 
| 
      
 21 
     | 
    
         
            +
                private
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
       17 
23 
     | 
    
         
             
                def do_authorize(response_params = {}, response_mode = param_or_nil("response_mode"))
         
     | 
| 
       18 
     | 
    
         
            -
                   
     | 
| 
      
 24 
     | 
    
         
            +
                  response_type = param("response_type")
         
     | 
| 
      
 25 
     | 
    
         
            +
                  return super unless response_type == "token" && supported_response_type?(response_type)
         
     | 
| 
       19 
26 
     | 
    
         | 
| 
       20 
27 
     | 
    
         
             
                  response_mode ||= "fragment"
         
     | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                  redirect_response_error("invalid_request") unless supported_response_mode?(response_mode)
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  oauth_grant = _do_authorize_token
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  response_params.replace(json_access_token_payload(oauth_grant))
         
     | 
| 
       22 
34 
     | 
    
         | 
| 
       23 
35 
     | 
    
         
             
                  response_params["state"] = param("state") if param_or_nil("state")
         
     | 
| 
       24 
36 
     | 
    
         | 
| 
       25 
37 
     | 
    
         
             
                  [response_params, response_mode]
         
     | 
| 
       26 
38 
     | 
    
         
             
                end
         
     | 
| 
       27 
39 
     | 
    
         | 
| 
       28 
     | 
    
         
            -
                def _do_authorize_token
         
     | 
| 
       29 
     | 
    
         
            -
                   
     | 
| 
       30 
     | 
    
         
            -
                     
     | 
| 
       31 
     | 
    
         
            -
                     
     | 
| 
       32 
     | 
    
         
            -
                     
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
     | 
    
         
            -
                   
     | 
| 
      
 40 
     | 
    
         
            +
                def _do_authorize_token(grant_params = {})
         
     | 
| 
      
 41 
     | 
    
         
            +
                  grant_params = {
         
     | 
| 
      
 42 
     | 
    
         
            +
                    oauth_grants_type_column => "implicit",
         
     | 
| 
      
 43 
     | 
    
         
            +
                    oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
         
     | 
| 
      
 44 
     | 
    
         
            +
                    oauth_grants_scopes_column => scopes,
         
     | 
| 
      
 45 
     | 
    
         
            +
                    oauth_grants_account_id_column => account_id
         
     | 
| 
      
 46 
     | 
    
         
            +
                  }.merge(grant_params)
         
     | 
| 
       35 
47 
     | 
    
         | 
| 
       36 
     | 
    
         
            -
                   
     | 
| 
      
 48 
     | 
    
         
            +
                  generate_token(grant_params, false)
         
     | 
| 
       37 
49 
     | 
    
         
             
                end
         
     | 
| 
       38 
50 
     | 
    
         | 
| 
       39 
51 
     | 
    
         
             
                def authorize_response(params, mode)
         
     | 
| 
         @@ -46,18 +58,8 @@ module Rodauth 
     | 
|
| 
       46 
58 
     | 
    
         
             
                  redirect(redirect_url.to_s)
         
     | 
| 
       47 
59 
     | 
    
         
             
                end
         
     | 
| 
       48 
60 
     | 
    
         | 
| 
       49 
     | 
    
         
            -
                def oauth_server_metadata_body(*)
         
     | 
| 
       50 
     | 
    
         
            -
                  super.tap do |data|
         
     | 
| 
       51 
     | 
    
         
            -
                    if use_oauth_implicit_grant_type?
         
     | 
| 
       52 
     | 
    
         
            -
                      data[:response_types_supported] << "token"
         
     | 
| 
       53 
     | 
    
         
            -
                      data[:response_modes_supported] << "fragment"
         
     | 
| 
       54 
     | 
    
         
            -
                      data[:grant_types_supported] << "implicit"
         
     | 
| 
       55 
     | 
    
         
            -
                    end
         
     | 
| 
       56 
     | 
    
         
            -
                  end
         
     | 
| 
       57 
     | 
    
         
            -
                end
         
     | 
| 
       58 
     | 
    
         
            -
             
     | 
| 
       59 
61 
     | 
    
         
             
                def check_valid_response_type?
         
     | 
| 
       60 
     | 
    
         
            -
                  return true if  
     | 
| 
      
 62 
     | 
    
         
            +
                  return true if param_or_nil("response_type") == "token"
         
     | 
| 
       61 
63 
     | 
    
         | 
| 
       62 
64 
     | 
    
         
             
                  super
         
     | 
| 
       63 
65 
     | 
    
         
             
                end
         
     |