Last active
June 24, 2021 00:26
-
-
Save jeremyevans/dabf95f367109428686efa1c06b59071 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 0bc589efb09312e67cd84c600fdd5e7540c04d11 Mon Sep 17 00:00:00 2001 | |
From: Jeremy Evans <[email protected]> | |
Date: Tue, 22 Jun 2021 11:27:19 -0700 | |
Subject: [PATCH] WIP on internal requests | |
--- | |
README.rdoc | 2 + | |
doc/internal_request.rdoc | 144 ++++++++++ | |
lib/rodauth.rb | 64 ++++- | |
lib/rodauth/features/base.rb | 4 + | |
lib/rodauth/features/change_login.rb | 2 + | |
lib/rodauth/features/change_password.rb | 2 + | |
lib/rodauth/features/close_account.rb | 2 + | |
lib/rodauth/features/create_account.rb | 2 + | |
lib/rodauth/features/email_auth.rb | 4 + | |
lib/rodauth/features/internal_request.rb | 287 ++++++++++++++++++++ | |
lib/rodauth/features/lockout.rb | 13 +- | |
lib/rodauth/features/login.rb | 3 + | |
lib/rodauth/features/otp.rb | 6 + | |
lib/rodauth/features/recovery_codes.rb | 4 + | |
lib/rodauth/features/remember.rb | 12 +- | |
lib/rodauth/features/reset_password.rb | 3 + | |
lib/rodauth/features/sms_codes.rb | 7 + | |
lib/rodauth/features/two_factor_base.rb | 2 + | |
lib/rodauth/features/verify_account.rb | 3 + | |
lib/rodauth/features/verify_login_change.rb | 2 + | |
spec/change_login_spec.rb | 27 ++ | |
spec/change_password_spec.rb | 37 +++ | |
spec/close_account_spec.rb | 25 ++ | |
spec/create_account_spec.rb | 24 ++ | |
spec/email_auth_spec.rb | 49 ++++ | |
spec/lockout_spec.rb | 83 ++++++ | |
spec/login_spec.rb | 35 +++ | |
spec/remember_spec.rb | 58 ++++ | |
spec/reset_password_spec.rb | 78 ++++++ | |
spec/rodauth_spec.rb | 133 +++++++++ | |
spec/spec_helper.rb | 2 +- | |
spec/two_factor_spec.rb | 244 +++++++++++++++++ | |
spec/verify_account_spec.rb | 92 +++++++ | |
spec/verify_login_change_spec.rb | 49 ++++ | |
www/pages/documentation.erb | 1 + | |
www/pages/why.erb | 1 + | |
36 files changed, 1490 insertions(+), 16 deletions(-) | |
create mode 100644 doc/internal_request.rdoc | |
create mode 100644 lib/rodauth/features/internal_request.rb | |
diff --git a/README.rdoc b/README.rdoc | |
index 3d7a95b..39bcd72 100644 | |
--- a/README.rdoc | |
+++ b/README.rdoc | |
@@ -60,6 +60,7 @@ HTML and JSON API for all supported features. | |
* Argon2 | |
* HTTP Basic Auth | |
* Change Password Notify | |
+* Internal Request | |
== Resources | |
@@ -879,6 +880,7 @@ view the appropriate file in the doc directory. | |
* {Disallow Password Reuse}[rdoc-ref:doc/disallow_password_reuse.rdoc] | |
* {Email Authentication}[rdoc-ref:doc/email_auth.rdoc] | |
* {HTTP Basic Auth}[rdoc-ref:doc/http_basic_auth.rdoc] | |
+* {Internal Request}[rdoc-ref:doc/internal_request.rdoc] | |
* {JSON}[rdoc-ref:doc/json.rdoc] | |
* {JWT CORS}[rdoc-ref:doc/jwt_cors.rdoc] | |
* {JWT Refresh}[rdoc-ref:doc/jwt_refresh.rdoc] | |
diff --git a/doc/internal_request.rdoc b/doc/internal_request.rdoc | |
new file mode 100644 | |
index 0000000..f9444d4 | |
--- /dev/null | |
+++ b/doc/internal_request.rdoc | |
@@ -0,0 +1,144 @@ | |
+= Documentation for Internal Request Feature | |
+ | |
+The internal request feature allows interacting with Rodauth by | |
+calling methods, and is expected to be used mostly for administrative | |
+purposes. It allows for things like an changing a login or password | |
+for an existing user, without requiring that the user login to the | |
+system. The reason the feature is named +internal_request+ is that | |
+it internally submits requests to Rodauth, which are handled almost | |
+identically to how actual web requests will be handled by Rodauth. | |
+ | |
+The general form of calling these methods is: | |
+ | |
+ App.rodauth.internal_request_method(hash) | |
+ | |
+Where +App+ is the Roda class, and +internal_request_method+ is the | |
+method you are calling. For example: | |
+ | |
+ App.rodauth.change_password(account_id: 1, password: 'foobar') | |
+ | |
+Will change the password for the account with id 1 to +foobar+. | |
+ | |
+All internal request methods support the following options. For | |
+internal requests that require an existing account, you should | |
+generally use one of the two following options: | |
+ | |
+:account_id :: The id of the account to be considered as logged in when the internal request is submitted (most internal requests require a login). This value is assumed to an existing account, and is not checked. | |
+:account_login :: The login of the account to be considered as logged in when the internal request is submitted (most internal requests require a login). This will query the database to determine the account's id before submitting the request. If there is no non-closed account for the login, this will raise an exception. | |
+ | |
+There are additional options available, that you should only use | |
+if you have special requirements: | |
+ | |
+:authenticated_by :: The array of strings to use for how the internal request's session was authenticated. | |
+:env :: A hash for the environment to use. | |
+:session :: A hash for the session to use. | |
+ | |
+All remaining options are considered parameters. Using the | |
+previous example: | |
+ | |
+ App.rodauth.change_password(account_id: 1, password: 'foobar') | |
+ | |
+The <tt>password: 'foobar'</tt> part means that the parameters | |
+for the request will be <tt>{rodauth.password_param => 'foobar'}</tt>, | |
+where +rodauth.password_param+ is the value of +password_param+ in | |
+your Rodauth configuration (this defaults to <tt>"password"</tt>). | |
+ | |
+Passing any options that are not valid Rodauth parameters will | |
+result in a warning. | |
+ | |
+== Configuration | |
+ | |
+In general, the configuration for internal requests is almost | |
+the same as for regular requests. There are some minor changes | |
+for easier usability. +modifications_require_password?+, | |
++require_login_confirmation?+, and +require_password_confirmation?+ | |
+are set to false. In general, the caller of the method should not | |
+be able to determine the user's password, and there is no point | |
+in requiring parameter confirmation when calling the method | |
+directly. | |
+ | |
+You can override the configuration for internal requests by using | |
+the +internal_request_configuration+ configuration method. For | |
+example, you can set the minimum length for logins to be 15 | |
+for normal requests, but only 3 for internal requests: | |
+ | |
+ plugin :rodauth do | |
+ enable :create_account, :internal_request | |
+ login_minimum_length 15 | |
+ | |
+ internal_request_configuration do | |
+ login_minimum_length 3 | |
+ end | |
+ end | |
+ | |
+Another approach for doing this is to call the +internal_request?+ | |
+method inside configuration method blocks: | |
+ | |
+ plugin :rodauth do | |
+ enable :create_account, :internal_request | |
+ login_minimum_length{internal_request? ? 3 : 15} | |
+ end | |
+ | |
+== Return Values and Exceptions | |
+ | |
+Internal request methods ending in a question mark return true or false. | |
+ | |
+Most other internal request methods return nil on success, and or raise a | |
+Rodauth::InternalRequestError exception on failure. The exception | |
+message will be the {the reason for the failure}[rdoc-ref:doc/error_reasons.rdoc], | |
+as a string. | |
+ | |
+If an internal request method returns a non-nil value on success, | |
+it will be documented in the section below. | |
+ | |
+== Features | |
+ | |
+This section documents the methods that are available for each | |
+feature. You must load that feature and the internal request | |
+feature in order to call the internal request methods for that | |
+feature. Some features support multiple internal request | |
+methods, and each internal request method supported will be | |
+documented under the appropriate subheading. If the method | |
+subheading states it it requires an account, you must pass | |
+the +:account_id+ or +account_login+ option when calling | |
+the method. | |
+ | |
+=== Change Login | |
+ | |
+==== change_login (requires account) | |
+ | |
+The +change_login+ method changes the login for the account. | |
+ | |
+The +:login+ option should be specified for the new login. | |
+Note that if the +:account_login+ option is specified, that | |
+is the current login for the account, not the new login. | |
+ | |
+=== Change Password | |
+ | |
+==== change_password (requires account) | |
+ | |
+The +change_password+ method changes the password for the | |
+account. | |
+ | |
+The +:password+ or +:new_password+ option should be | |
+specified for the new password. | |
+ | |
+=== Close Account | |
+ | |
+==== close_account (requires account) | |
+ | |
+The +close_account+ method closes the account. There | |
+is no method in Rodauth to reopen closed accounts. | |
+ | |
+=== Create Account | |
+=== Email Auth | |
+=== Lockout | |
+=== Login | |
+=== OTP | |
+=== Recovery Codes | |
+=== Remember | |
+=== Reset Password | |
+=== SMS Codes | |
+=== Two Factor Base | |
+=== Verify Account | |
+=== Verify Login Change | |
diff --git a/lib/rodauth.rb b/lib/rodauth.rb | |
index cf59ae1..1cce36d 100644 | |
--- a/lib/rodauth.rb | |
+++ b/lib/rodauth.rb | |
@@ -39,11 +39,11 @@ module Rodauth | |
else | |
json_opt != :only | |
end | |
- auth_class = (app.opts[:rodauths] ||= {})[opts[:name]] ||= opts[:auth_class] || Class.new(Auth) | |
+ auth_class = (app.opts[:rodauths] ||= {})[opts[:name]] ||= opts[:auth_class] || Class.new(Auth){@configuration_name = opts[:name]} | |
if !auth_class.roda_class | |
auth_class.roda_class = app | |
elsif auth_class.roda_class != app | |
- auth_class = app.opts[:rodauths][opts[:name]] = Class.new(auth_class) | |
+ auth_class = app.opts[:rodauths][opts[:name]] = Class.new(auth_class){@configuration_name = opts[:name]} | |
auth_class.roda_class = app | |
end | |
auth_class.configure(&block) if block | |
@@ -107,6 +107,7 @@ module Rodauth | |
attr_accessor :dependencies | |
attr_accessor :routes | |
attr_accessor :configuration | |
+ attr_accessor :internal_request_methods | |
def route(name=feature_name, default=name.to_s.tr('_', '-'), &block) | |
route_meth = :"#{name}_route" | |
@@ -152,6 +153,21 @@ module Rodauth | |
FEATURES[name] = feature | |
end | |
+ def internal_request_method(name=feature_name) | |
+ (@internal_request_methods ||= []) << name | |
+ end | |
+ | |
+ def class_methods(&block) | |
+ if defined?(self::ClassMethods) | |
+ mod = self::ClassMethods | |
+ else | |
+ mod = Module.new | |
+ const_set(:ClassMethods, mod) | |
+ end | |
+ mod.module_eval(&block) | |
+ nil | |
+ end | |
+ | |
def configuration_module_eval(&block) | |
configuration.module_eval(&block) | |
end | |
@@ -254,36 +270,54 @@ module Rodauth | |
end | |
end | |
- class Auth | |
- class << self | |
- attr_accessor :roda_class | |
- attr_reader :features | |
- attr_reader :routes | |
- attr_accessor :route_hash | |
+ module AuthClassMethods | |
+ attr_accessor :roda_class | |
+ attr_reader :features | |
+ attr_reader :routes | |
+ attr_reader :internal_request_methods | |
+ attr_accessor :route_hash | |
+ attr_reader :configuration_name | |
+ attr_reader :configuration | |
+ | |
+ def initialize_clone(klass) | |
+ super | |
+ @roda_class = klass.roda_class | |
+ @features = klass.features.clone | |
+ @routes = klass.routes.clone | |
+ @internal_request_methods = klass.internal_request_methods.clone | |
+ @route_hash = klass.route_hash.clone | |
+ @configuration = klass.configuration.clone | |
+ @configuration.instance_variable_set(:@auth, self) | |
end | |
- def self.inherited(subclass) | |
+ def inherited(subclass) | |
super | |
subclass.instance_exec do | |
@features = [] | |
@routes = [] | |
+ @internal_request_methods = [] | |
@route_hash = {} | |
@configuration = Configuration.new(self) | |
end | |
end | |
- def self.configure(&block) | |
+ def configure(&block) | |
@configuration.apply(&block) | |
end | |
- def self.freeze | |
+ def freeze | |
@features.freeze | |
@routes.freeze | |
+ @internal_request_methods.freeze | |
@route_hash.freeze | |
super | |
end | |
end | |
+ class Auth | |
+ extend AuthClassMethods | |
+ end | |
+ | |
class Configuration | |
attr_reader :auth | |
@@ -320,6 +354,14 @@ module Rodauth | |
@auth.routes.concat(feature.routes) | |
@auth.send(:include, feature) | |
+ | |
+ if defined?(feature::ClassMethods) | |
+ @auth.extend(feature::ClassMethods) | |
+ end | |
+ | |
+ if feature.internal_request_methods | |
+ @auth.internal_request_methods.concat(feature.internal_request_methods) | |
+ end | |
end | |
end | |
diff --git a/lib/rodauth/features/base.rb b/lib/rodauth/features/base.rb | |
index 2283436..fb9b2eb 100644 | |
--- a/lib/rodauth/features/base.rb | |
+++ b/lib/rodauth/features/base.rb | |
@@ -734,6 +734,10 @@ module Rodauth | |
end | |
end | |
+ def internal_request? | |
+ false | |
+ end | |
+ | |
def set_session_value(key, value) | |
session[key] = value | |
end | |
diff --git a/lib/rodauth/features/change_login.rb b/lib/rodauth/features/change_login.rb | |
index 3245f1b..8011ed5 100644 | |
--- a/lib/rodauth/features/change_login.rb | |
+++ b/lib/rodauth/features/change_login.rb | |
@@ -19,6 +19,8 @@ module Rodauth | |
auth_methods :change_login | |
+ internal_request_method | |
+ | |
route do |r| | |
require_account | |
before_change_login_route | |
diff --git a/lib/rodauth/features/change_password.rb b/lib/rodauth/features/change_password.rb | |
index 4aeeac0..23eeb51 100644 | |
--- a/lib/rodauth/features/change_password.rb | |
+++ b/lib/rodauth/features/change_password.rb | |
@@ -22,6 +22,8 @@ module Rodauth | |
:invalid_previous_password_message | |
) | |
+ internal_request_method | |
+ | |
route do |r| | |
require_account | |
before_change_password_route | |
diff --git a/lib/rodauth/features/close_account.rb b/lib/rodauth/features/close_account.rb | |
index 817f9aa..d5df8ca 100644 | |
--- a/lib/rodauth/features/close_account.rb | |
+++ b/lib/rodauth/features/close_account.rb | |
@@ -24,6 +24,8 @@ module Rodauth | |
:delete_account | |
) | |
+ internal_request_method | |
+ | |
route do |r| | |
require_account | |
before_close_account_route | |
diff --git a/lib/rodauth/features/create_account.rb b/lib/rodauth/features/create_account.rb | |
index 0f3aafb..3645e2b 100644 | |
--- a/lib/rodauth/features/create_account.rb | |
+++ b/lib/rodauth/features/create_account.rb | |
@@ -27,6 +27,8 @@ module Rodauth | |
:new_account | |
) | |
+ internal_request_method | |
+ | |
route do |r| | |
check_already_logged_in | |
before_create_account_route | |
diff --git a/lib/rodauth/features/email_auth.rb b/lib/rodauth/features/email_auth.rb | |
index 63f28be..7aae4c2 100644 | |
--- a/lib/rodauth/features/email_auth.rb | |
+++ b/lib/rodauth/features/email_auth.rb | |
@@ -49,6 +49,10 @@ module Rodauth | |
auth_private_methods :account_from_email_auth_key | |
+ internal_request_method | |
+ internal_request_method :email_auth_request | |
+ internal_request_method :valid_email_auth? | |
+ | |
route(:email_auth_request) do |r| | |
check_already_logged_in | |
before_email_auth_request_route | |
diff --git a/lib/rodauth/features/internal_request.rb b/lib/rodauth/features/internal_request.rb | |
new file mode 100644 | |
index 0000000..21e6609 | |
--- /dev/null | |
+++ b/lib/rodauth/features/internal_request.rb | |
@@ -0,0 +1,287 @@ | |
+# frozen-string-literal: true | |
+ | |
+require 'stringio' | |
+ | |
+module Rodauth | |
+ class InternalRequestError < StandardError | |
+ end | |
+ | |
+ module InternalRequestMethods | |
+ attr_accessor :session | |
+ attr_accessor :params | |
+ attr_reader :flash | |
+ attr_reader :internal_request_return_value | |
+ | |
+ def raw_param(k) | |
+ @params[k] | |
+ end | |
+ | |
+ def set_error_flash(message) | |
+ @flash = message | |
+ _handle_internal_request_error(message) | |
+ end | |
+ alias set_redirect_error_flash set_error_flash | |
+ alias set_error_reason set_error_flash | |
+ private :set_error_reason | |
+ | |
+ def set_notice_flash(message) | |
+ @flash = message | |
+ end | |
+ alias set_notice_now_flash set_notice_flash | |
+ | |
+ def modifications_require_password? | |
+ false | |
+ end | |
+ alias require_login_confirmation? modifications_require_password? | |
+ alias require_password_confirmation? modifications_require_password? | |
+ | |
+ def otp_setup_view | |
+ _return_from_internal_request([otp_user_key, otp_key]) | |
+ end | |
+ | |
+ def add_recovery_codes_view | |
+ _return_from_internal_request(recovery_codes) | |
+ end | |
+ | |
+ private | |
+ | |
+ def internal_request? | |
+ true | |
+ end | |
+ | |
+ def after_login | |
+ super | |
+ _set_internal_request_return_value(account_id) unless @return_false_on_error | |
+ end | |
+ | |
+ def after_remember | |
+ super | |
+ if params[remember_param] == remember_remember_param_value | |
+ _set_internal_request_return_value("#{account_id}_#{convert_token_key(remember_key_value)}") | |
+ end | |
+ end | |
+ | |
+ def after_load_memory | |
+ super | |
+ _set_internal_request_return_value(session_value) | |
+ end | |
+ | |
+ def before_change_password_route | |
+ super | |
+ params[new_password_param] ||= params[password_param] | |
+ end | |
+ | |
+ def before_email_auth_request_route | |
+ super | |
+ _set_login_param_from_account | |
+ end | |
+ | |
+ def before_login_route | |
+ super | |
+ _set_login_param_from_account | |
+ end | |
+ | |
+ def before_unlock_account_request_route | |
+ super | |
+ _set_login_param_from_account | |
+ end | |
+ | |
+ def before_reset_password_request_route | |
+ super | |
+ _set_login_param_from_account | |
+ end | |
+ | |
+ def before_verify_account_resend_route | |
+ super | |
+ _set_login_param_from_account | |
+ end | |
+ | |
+ def account_from_key(token, status_id=nil) | |
+ return super unless session_value | |
+ return unless yield session_value | |
+ ds = account_ds(session_value) | |
+ ds = ds.where(account_status_column=>status_id) if status_id && !skip_status_checks? | |
+ ds.first | |
+ end | |
+ | |
+ def _set_internal_request_return_value(value) | |
+ @internal_request_return_value = value | |
+ end | |
+ | |
+ def _return_from_internal_request(value) | |
+ _set_internal_request_return_value(value) | |
+ throw(:halt) | |
+ end | |
+ | |
+ def _handle_internal_request_error(message) | |
+ if @return_false_on_error | |
+ _return_from_internal_request(false) | |
+ else | |
+ raise InternalRequestError, message | |
+ end | |
+ end | |
+ | |
+ def _return_false_on_error! | |
+ @return_false_on_error = true | |
+ end | |
+ | |
+ def _set_login_param_from_account | |
+ if session_value && !params[login_param] && (account = account_ds(session_value).first) | |
+ params[login_param] = account[login_column] | |
+ end | |
+ end | |
+ | |
+ def _get_remember_cookie | |
+ params[remember_param] | |
+ end | |
+ | |
+ def _handle_lock_account(_) | |
+ @account = {account_id_column=>session_value} | |
+ raised_uniqueness_violation{account_lockouts_ds.insert(_setup_account_lockouts_hash(account_id, generate_unlock_account_key))} | |
+ end | |
+ | |
+ def _handle_remember_setup(request) | |
+ params[remember_param] = remember_remember_param_value | |
+ _handle_remember(request) | |
+ end | |
+ | |
+ def _handle_remember_disable(request) | |
+ params[remember_param] = remember_disable_param_value | |
+ _handle_remember(request) | |
+ end | |
+ | |
+ def _handle_account_id_for_remember_key(request) | |
+ load_memory | |
+ end | |
+ | |
+ def _handle_otp_setup_params(request) | |
+ request.env['REQUEST_METHOD'] = 'GET' | |
+ _handle_otp_setup(request) | |
+ end | |
+ | |
+ def _predicate_internal_request(meth, request) | |
+ _return_false_on_error! | |
+ _set_internal_request_return_value(true) | |
+ send(meth, request) | |
+ end | |
+ | |
+ def _handle_valid_login_and_password?(request) | |
+ _predicate_internal_request(:_handle_login, request) | |
+ end | |
+ | |
+ def _handle_valid_email_auth?(request) | |
+ _predicate_internal_request(:_handle_email_auth, request) | |
+ end | |
+ | |
+ def _handle_valid_otp_auth?(request) | |
+ _predicate_internal_request(:_handle_otp_auth, request) | |
+ end | |
+ | |
+ def _handle_valid_recovery_auth?(request) | |
+ _predicate_internal_request(:_handle_recovery_auth, request) | |
+ end | |
+ | |
+ def _handle_valid_sms_auth?(request) | |
+ _predicate_internal_request(:_handle_sms_auth, request) | |
+ end | |
+ end | |
+ | |
+ Feature.define(:internal_request, :InternalRequest) do | |
+ class_methods do | |
+ attr_reader :internal_request_configuration_blocks | |
+ | |
+ def internal_request(route, opts={}) | |
+ opts = opts.dup | |
+ | |
+ env = { | |
+ 'REQUEST_METHOD'=>'POST', | |
+ 'PATH_INFO'=>'/', | |
+ "SCRIPT_NAME" => "", | |
+ "HTTP_HOST" => "invalidurl @@.com", | |
+ "SERVER_NAME" => 'invalidurl @@.com', | |
+ "CONTENT_TYPE" => "application/x-www-form-urlencoded", | |
+ "rack.input"=>StringIO.new(''), | |
+ "rack.url_scheme"=>"https" | |
+ } | |
+ env.merge!(opts.delete(:env)) if opts[:env] | |
+ | |
+ session = {} | |
+ session.merge!(opts.delete(:session)) if opts[:session] | |
+ | |
+ params = {} | |
+ | |
+ rodauth = roda_class.new(env).rodauth(configuration_name) | |
+ rodauth.session = session | |
+ rodauth.params = params | |
+ | |
+ unless account_id = opts.delete(:account_id) | |
+ if (account_login = opts.delete(:account_login)) | |
+ if (account = rodauth.send(:_account_from_login, account_login)) | |
+ account_id = account[rodauth.account_id_column] | |
+ else | |
+ raise InternalRequestError, "no account for login: #{account_login.inspect}" | |
+ end | |
+ end | |
+ end | |
+ | |
+ if account_id | |
+ session[rodauth.session_key] = account_id | |
+ unless authenticated_by = opts.delete(:authenticated_by) | |
+ authenticated_by = case route | |
+ when :otp_auth, :sms_request, :sms_auth, :recovery_auth, :valid_otp_auth?, :valid_sms_auth?, :valid_recovery_auth? | |
+ ['internal1'] | |
+ else | |
+ ['internal1', 'internal2'] | |
+ end | |
+ end | |
+ session[rodauth.authenticated_by_session_key] = authenticated_by | |
+ end | |
+ | |
+ opts.keys.each do |k| | |
+ meth = :"#{k}_param" | |
+ params[rodauth.public_send(meth).to_s] = opts.delete(k) if rodauth.respond_to?(meth) | |
+ end | |
+ | |
+ unless opts.empty? | |
+ warn "unhandled options passed to #{route}: #{opts.inspect}" | |
+ end | |
+ | |
+ catch(:halt) do | |
+ rodauth.send(:"_handle_#{route}", rodauth.request) | |
+ end | |
+ | |
+ rodauth.internal_request_return_value | |
+ end | |
+ end | |
+ | |
+ configuration_module_eval do | |
+ def internal_request_configuration(&block) | |
+ @auth.instance_exec do | |
+ (@internal_request_configuration_blocks ||= []) << block | |
+ end | |
+ end | |
+ end | |
+ | |
+ def post_configure | |
+ super | |
+ | |
+ klass = self.class | |
+ internal_class = klass.clone | |
+ internal_class.send(:include, InternalRequestMethods) | |
+ configuration_name = Object.new | |
+ internal_class.instance_variable_set(:@configuration_name, configuration_name) | |
+ klass.roda_class.plugin(:rodauth, :name=>configuration_name, :auth_class=>internal_class) | |
+ | |
+ if blocks = klass.internal_request_configuration_blocks | |
+ configuration = internal_class.configuration | |
+ blocks.each do |block| | |
+ configuration.instance_exec(&block) | |
+ end | |
+ end | |
+ | |
+ klass.internal_request_methods.each do |name| | |
+ klass.define_singleton_method(name){|opts={}| internal_class.internal_request(name, opts)} | |
+ end | |
+ end | |
+ end | |
+end | |
diff --git a/lib/rodauth/features/lockout.rb b/lib/rodauth/features/lockout.rb | |
index 04ab989..189ec54 100644 | |
--- a/lib/rodauth/features/lockout.rb | |
+++ b/lib/rodauth/features/lockout.rb | |
@@ -62,6 +62,10 @@ module Rodauth | |
) | |
auth_private_methods :account_from_unlock_key | |
+ internal_request_method(:lock_account) | |
+ internal_request_method(:unlock_account_request) | |
+ internal_request_method(:unlock_account) | |
+ | |
route(:unlock_account_request) do |r| | |
check_already_logged_in | |
before_unlock_account_request_route | |
@@ -167,6 +171,12 @@ module Rodauth | |
unlock_account | |
end | |
+ def _setup_account_lockouts_hash(account_id, key) | |
+ hash = {account_lockouts_id_column=>account_id, account_lockouts_key_column=>key} | |
+ set_deadline_value(hash, account_lockouts_deadline_column, account_lockouts_deadline_interval) | |
+ hash | |
+ end | |
+ | |
def invalid_login_attempted | |
ds = account_login_failures_ds. | |
where(account_login_failures_id_column=>account_id) | |
@@ -192,8 +202,7 @@ module Rodauth | |
if number >= max_invalid_logins | |
@unlock_account_key_value = generate_unlock_account_key | |
- hash = {account_lockouts_id_column=>account_id, account_lockouts_key_column=>unlock_account_key_value} | |
- set_deadline_value(hash, account_lockouts_deadline_column, account_lockouts_deadline_interval) | |
+ hash = _setup_account_lockouts_hash(account_id, unlock_account_key_value) | |
if e = raised_uniqueness_violation{account_lockouts_ds.insert(hash)} | |
# If inserting into the lockout table raises a violation, we should just be able to pull the already inserted | |
diff --git a/lib/rodauth/features/login.rb b/lib/rodauth/features/login.rb | |
index e3935da..6edc771 100644 | |
--- a/lib/rodauth/features/login.rb | |
+++ b/lib/rodauth/features/login.rb | |
@@ -25,6 +25,9 @@ module Rodauth | |
auth_value_methods :login_return_to_requested_location_path | |
+ internal_request_method | |
+ internal_request_method :valid_login_and_password? | |
+ | |
route do |r| | |
check_already_logged_in | |
before_login_route | |
diff --git a/lib/rodauth/features/otp.rb b/lib/rodauth/features/otp.rb | |
index 269b23b..eeb1497 100644 | |
--- a/lib/rodauth/features/otp.rb | |
+++ b/lib/rodauth/features/otp.rb | |
@@ -96,6 +96,12 @@ module Rodauth | |
:otp_tmp_key | |
) | |
+ internal_request_method :otp_setup_params | |
+ internal_request_method :otp_setup | |
+ internal_request_method :otp_auth | |
+ internal_request_method :valid_otp_auth? | |
+ internal_request_method :otp_disable | |
+ | |
route(:otp_auth) do |r| | |
require_login | |
require_account_session | |
diff --git a/lib/rodauth/features/recovery_codes.rb b/lib/rodauth/features/recovery_codes.rb | |
index 2a396e3..4840f66 100644 | |
--- a/lib/rodauth/features/recovery_codes.rb | |
+++ b/lib/rodauth/features/recovery_codes.rb | |
@@ -59,6 +59,10 @@ module Rodauth | |
:recovery_code_match?, | |
) | |
+ internal_request_method :recovery_codes | |
+ internal_request_method :recovery_auth | |
+ internal_request_method :valid_recovery_auth? | |
+ | |
route(:recovery_auth) do |r| | |
require_login | |
require_account_session | |
diff --git a/lib/rodauth/features/remember.rb b/lib/rodauth/features/remember.rb | |
index bce0725..d95983d 100644 | |
--- a/lib/rodauth/features/remember.rb | |
+++ b/lib/rodauth/features/remember.rb | |
@@ -46,6 +46,10 @@ module Rodauth | |
:remove_remember_key | |
) | |
+ internal_request_method :remember_setup | |
+ internal_request_method :remember_disable | |
+ internal_request_method :account_id_for_remember_key | |
+ | |
route do |r| | |
require_account | |
before_remember_route | |
@@ -83,7 +87,7 @@ module Rodauth | |
end | |
def remembered_session_id | |
- return unless cookie = request.cookies[remember_cookie_key] | |
+ return unless cookie = _get_remember_cookie | |
id, key = cookie.split('_', 2) | |
return unless id && key | |
@@ -110,7 +114,7 @@ module Rodauth | |
unless id = remembered_session_id | |
# Only set expired cookie if there is already a cookie set. | |
- forget_login if request.cookies[remember_cookie_key] | |
+ forget_login if _get_remember_cookie | |
return | |
end | |
@@ -187,6 +191,10 @@ module Rodauth | |
private | |
+ def _get_remember_cookie | |
+ request.cookies[remember_cookie_key] | |
+ end | |
+ | |
def after_logout | |
forget_login | |
super if defined?(super) | |
diff --git a/lib/rodauth/features/reset_password.rb b/lib/rodauth/features/reset_password.rb | |
index 495cc87..697321e 100644 | |
--- a/lib/rodauth/features/reset_password.rb | |
+++ b/lib/rodauth/features/reset_password.rb | |
@@ -57,6 +57,9 @@ module Rodauth | |
:account_from_reset_password_key | |
) | |
+ internal_request_method(:reset_password_request) | |
+ internal_request_method | |
+ | |
route(:reset_password_request) do |r| | |
check_already_logged_in | |
before_reset_password_request_route | |
diff --git a/lib/rodauth/features/sms_codes.rb b/lib/rodauth/features/sms_codes.rb | |
index f757b3e..afeb29c 100644 | |
--- a/lib/rodauth/features/sms_codes.rb | |
+++ b/lib/rodauth/features/sms_codes.rb | |
@@ -112,6 +112,13 @@ module Rodauth | |
:sms_valid_phone? | |
) | |
+ internal_request_method :sms_setup | |
+ internal_request_method :sms_confirm | |
+ internal_request_method :sms_request | |
+ internal_request_method :sms_auth | |
+ internal_request_method :valid_sms_auth? | |
+ internal_request_method :sms_disable | |
+ | |
route(:sms_request) do |r| | |
require_login | |
require_account_session | |
diff --git a/lib/rodauth/features/two_factor_base.rb b/lib/rodauth/features/two_factor_base.rb | |
index afedd73..d92640c 100644 | |
--- a/lib/rodauth/features/two_factor_base.rb | |
+++ b/lib/rodauth/features/two_factor_base.rb | |
@@ -57,6 +57,8 @@ module Rodauth | |
:two_factor_update_session | |
) | |
+ internal_request_method :two_factor_disable | |
+ | |
route(:two_factor_manage, 'multifactor-manage') do |r| | |
require_account | |
before_two_factor_manage_route | |
diff --git a/lib/rodauth/features/verify_account.rb b/lib/rodauth/features/verify_account.rb | |
index 4f3a439..81795e1 100644 | |
--- a/lib/rodauth/features/verify_account.rb | |
+++ b/lib/rodauth/features/verify_account.rb | |
@@ -60,6 +60,9 @@ module Rodauth | |
:account_from_verify_account_key | |
) | |
+ internal_request_method(:verify_account_resend) | |
+ internal_request_method | |
+ | |
route(:verify_account_resend) do |r| | |
verify_account_check_already_logged_in | |
before_verify_account_resend_route | |
diff --git a/lib/rodauth/features/verify_login_change.rb b/lib/rodauth/features/verify_login_change.rb | |
index c022fe8..e903cbc 100644 | |
--- a/lib/rodauth/features/verify_login_change.rb | |
+++ b/lib/rodauth/features/verify_login_change.rb | |
@@ -50,6 +50,8 @@ module Rodauth | |
:account_from_verify_login_change_key | |
) | |
+ internal_request_method | |
+ | |
route do |r| | |
before_verify_login_change_route | |
diff --git a/spec/change_login_spec.rb b/spec/change_login_spec.rb | |
index a300e23..26a1e26 100644 | |
--- a/spec/change_login_spec.rb | |
+++ b/spec/change_login_spec.rb | |
@@ -169,4 +169,31 @@ describe 'Rodauth change_login feature' do | |
json_login(:login=>'[email protected]') | |
end | |
end | |
+ | |
+ it "should support changing logins using an internal request" do | |
+ rodauth do | |
+ enable :login, :change_login, :internal_request | |
+ login_meets_requirements?{|login| login.length > 4} | |
+ end | |
+ roda do |r| | |
+ r.rodauth | |
+ r.root{rodauth.logged_in?.nil?.to_s} | |
+ end | |
+ | |
+ proc do | |
+ app.rodauth.change_login(:account_login=>'[email protected]', :login=>'foo') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ app.rodauth.change_login(:account_login=>'[email protected]', :login=>'[email protected]').must_be_nil | |
+ | |
+ visit '/' | |
+ page.body.must_equal 'true' | |
+ | |
+ login | |
+ page.current_path.must_equal '/login' | |
+ | |
+ login(:login=>'[email protected]', :visit=>false) | |
+ page.current_path.must_equal '/' | |
+ page.body.must_equal 'false' | |
+ end | |
end | |
diff --git a/spec/change_password_spec.rb b/spec/change_password_spec.rb | |
index ae67bc8..31a9782 100644 | |
--- a/spec/change_password_spec.rb | |
+++ b/spec/change_password_spec.rb | |
@@ -201,4 +201,41 @@ describe 'Rodauth change_password feature' do | |
json_login(:pass=>'012345678') | |
end | |
end | |
+ | |
+ it "should support changing passwords using an internal request" do | |
+ rodauth do | |
+ enable :login, :logout, :change_password, :internal_request | |
+ end | |
+ roda do |r| | |
+ r.rodauth | |
+ r.root{rodauth.logged_in?.nil?.to_s} | |
+ end | |
+ | |
+ proc do | |
+ app.rodauth.change_password(:account_login=>'[email protected]', :new_password=>'foo') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ proc do | |
+ app.rodauth.change_password(:account_login=>'[email protected]', :password=>'foo') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ app.rodauth.change_password(:account_login=>'[email protected]', :new_password=>'0123456').must_be_nil | |
+ | |
+ visit '/' | |
+ page.body.must_equal 'true' | |
+ | |
+ login | |
+ page.current_path.must_equal '/login' | |
+ | |
+ login(:pass=>'0123456') | |
+ page.current_path.must_equal '/' | |
+ page.body.must_equal 'false' | |
+ logout | |
+ | |
+ app.rodauth.change_password(:account_login=>'[email protected]', :password=>'01234567').must_be_nil | |
+ | |
+ login(:pass=>'01234567') | |
+ page.current_path.must_equal '/' | |
+ page.body.must_equal 'false' | |
+ end | |
end | |
diff --git a/spec/close_account_spec.rb b/spec/close_account_spec.rb | |
index c82bbdd..a16b6fb 100644 | |
--- a/spec/close_account_spec.rb | |
+++ b/spec/close_account_spec.rb | |
@@ -161,4 +161,29 @@ describe 'Rodauth close_account feature' do | |
DB[:accounts].select_map(:status_id).must_equal [3] | |
end | |
end | |
+ | |
+ it "should support closing accounts using an internal request" do | |
+ rodauth do | |
+ enable :login, :logout, :close_account, :internal_request | |
+ end | |
+ roda do |r| | |
+ r.rodauth | |
+ r.root{rodauth.logged_in?.nil?.to_s} | |
+ end | |
+ | |
+ visit '/' | |
+ page.body.must_equal 'true' | |
+ | |
+ login | |
+ page.body.must_equal 'false' | |
+ | |
+ logout | |
+ | |
+ app.rodauth.close_account(:account_login=>'[email protected]').must_be_nil | |
+ | |
+ login | |
+ page.current_path.must_equal '/login' | |
+ | |
+ DB[:accounts].select_map(:status_id).must_equal [3] | |
+ end | |
end | |
diff --git a/spec/create_account_spec.rb b/spec/create_account_spec.rb | |
index 1e20ba4..9e23441 100644 | |
--- a/spec/create_account_spec.rb | |
+++ b/spec/create_account_spec.rb | |
@@ -134,4 +134,28 @@ describe 'Rodauth create_account feature' do | |
json_login(:login=>'[email protected]') | |
end | |
end | |
+ | |
+ it "should support creating accounts using an internal request" do | |
+ rodauth do | |
+ enable :login, :create_account, :internal_request | |
+ end | |
+ roda do |r| | |
+ r.rodauth | |
+ r.root{rodauth.logged_in?.nil?.to_s} | |
+ end | |
+ | |
+ proc do | |
+ app.rodauth.create_account(:login=>'foo', :password=>'sdkjnlsalkklsda') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ proc do | |
+ app.rodauth.create_account(:login=>'[email protected]', :password=>'123') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ app.rodauth.create_account(:login=>'[email protected]', :password=>'sdkjnlsalkklsda').must_be_nil | |
+ | |
+ login(:login=>'[email protected]', :pass=>'sdkjnlsalkklsda') | |
+ page.current_path.must_equal '/' | |
+ page.body.must_equal 'false' | |
+ end | |
end | |
diff --git a/spec/email_auth_spec.rb b/spec/email_auth_spec.rb | |
index 91380c2..823474d 100644 | |
--- a/spec/email_auth_spec.rb | |
+++ b/spec/email_auth_spec.rb | |
@@ -340,4 +340,53 @@ describe 'Rodauth email auth feature' do | |
res.must_equal [200, {"success"=>"You have been logged in"}] | |
end | |
end | |
+ | |
+ it "should allow checking email auth using internal requests" do | |
+ rodauth do | |
+ enable :login, :logout, :email_auth, :internal_request | |
+ end | |
+ roda do |r| | |
+ r.rodauth | |
+ r.root{view :content=>""} | |
+ end | |
+ | |
+ visit '/login' | |
+ page.title.must_equal 'Login' | |
+ fill_in 'Login', :with=>'[email protected]' | |
+ click_button 'Login' | |
+ click_button 'Send Login Link Via Email' | |
+ link = email_link(/(\/email-auth\?key=.+)$/) | |
+ key = link.split('=').last | |
+ | |
+ app.rodauth.valid_email_auth?(:email_auth_key=>key[0...-1]).must_equal false | |
+ app.rodauth.valid_email_auth?(:email_auth_key=>key).must_equal true | |
+ | |
+ visit link | |
+ page.find('#error_flash').text.must_equal "There was an error logging you in: invalid email authentication key" | |
+ | |
+ app.rodauth.email_auth_request(:account_login=>'[email protected]').must_be_nil | |
+ link2 = email_link(/(\/email-auth\?key=.+)$/) | |
+ link2.wont_equal link | |
+ key = link2.split('=').last | |
+ | |
+ proc do | |
+ app.rodauth.email_auth(:email_auth_key=>key[0...-1]) | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ app.rodauth.email_auth(:email_auth_key=>key).must_equal DB[:accounts].get(:id) | |
+ | |
+ visit link | |
+ page.find('#error_flash').text.must_equal "There was an error logging you in: invalid email authentication key" | |
+ | |
+ app.rodauth.email_auth_request(:account_login=>'[email protected]').must_be_nil | |
+ link3 = email_link(/(\/email-auth\?key=.+)$/) | |
+ link3.wont_equal link | |
+ link3.wont_equal link2 | |
+ | |
+ visit link3 | |
+ page.title.must_equal 'Login' | |
+ click_button 'Login' | |
+ page.find('#notice_flash').text.must_equal 'You have been logged in' | |
+ page.current_path.must_equal '/' | |
+ end | |
end | |
diff --git a/spec/lockout_spec.rb b/spec/lockout_spec.rb | |
index c4fc31d..3fa970c 100644 | |
--- a/spec/lockout_spec.rb | |
+++ b/spec/lockout_spec.rb | |
@@ -278,4 +278,87 @@ describe 'Rodauth lockout feature' do | |
json_login | |
end | |
end | |
+ | |
+ it "should support account locks, unlocks, and unlock requests using internal requests" do | |
+ rodauth do | |
+ enable :lockout, :logout, :internal_request | |
+ account_lockouts_email_last_sent_column nil | |
+ end | |
+ roda do |r| | |
+ r.rodauth | |
+ r.root{view :content=>(rodauth.logged_in? ? "Logged In" : "Not Logged")} | |
+ end | |
+ | |
+ proc do | |
+ app.rodauth.lock_account(:account_login=>'[email protected]') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ proc do | |
+ app.rodauth.unlock_account_request(:account_login=>'[email protected]') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ proc do | |
+ app.rodauth.unlock_account(:account_login=>'[email protected]') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ proc do | |
+ app.rodauth.unlock_account_request(:login=>'[email protected]') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ proc do | |
+ app.rodauth.unlock_account_request(:account_login=>'[email protected]') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ proc do | |
+ app.rodauth.unlock_account(:account_login=>'[email protected]') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ app.rodauth.lock_account(:account_login=>'[email protected]').must_be_nil | |
+ | |
+ # Check idempotent | |
+ app.rodauth.lock_account(:account_login=>'[email protected]').must_be_nil | |
+ | |
+ login | |
+ page.find('#error_flash').text.must_equal "This account is currently locked out and cannot be logged in to" | |
+ | |
+ app.rodauth.unlock_account_request(:login=>'[email protected]').must_be_nil | |
+ link = email_link(/(\/unlock-account\?key=.+)$/) | |
+ | |
+ app.rodauth.unlock_account_request(:account_login=>'[email protected]').must_be_nil | |
+ link2 = email_link(/(\/unlock-account\?key=.+)$/) | |
+ link2.must_equal link | |
+ | |
+ visit link | |
+ click_button 'Unlock Account' | |
+ | |
+ page.find('#notice_flash').text.must_equal 'Your account has been unlocked' | |
+ page.body.must_include("Logged In") | |
+ | |
+ logout | |
+ | |
+ app.rodauth.lock_account(:account_login=>'[email protected]').must_be_nil | |
+ | |
+ login | |
+ page.find('#error_flash').text.must_equal "This account is currently locked out and cannot be logged in to" | |
+ | |
+ app.rodauth.unlock_account(:account_login=>'[email protected]').must_be_nil | |
+ | |
+ login | |
+ page.body.must_include 'Logged In' | |
+ | |
+ app.rodauth.lock_account(:account_login=>'[email protected]').must_be_nil | |
+ app.rodauth.unlock_account_request(:account_login=>'[email protected]').must_be_nil | |
+ link3 = email_link(/(\/unlock-account\?key=.+)$/) | |
+ link3.wont_equal link2 | |
+ key = link3.split('=').last | |
+ | |
+ proc do | |
+ app.rodauth.unlock_account(:unlock_account_key=>key[0...-1]) | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ app.rodauth.unlock_account(:unlock_account_key=>key).must_be_nil | |
+ | |
+ login | |
+ page.body.must_include 'Logged In' | |
+ end | |
end | |
diff --git a/spec/login_spec.rb b/spec/login_spec.rb | |
index 097f7eb..3dd79dc 100644 | |
--- a/spec/login_spec.rb | |
+++ b/spec/login_spec.rb | |
@@ -438,4 +438,39 @@ describe 'Rodauth login feature' do | |
json_request.must_equal [200, 2] | |
end | |
end | |
+ | |
+ it "should allow checking login and password using internal requests" do | |
+ rodauth do | |
+ enable :login, :internal_request | |
+ end | |
+ roda do |r| | |
+ end | |
+ | |
+ app.rodauth.valid_login_and_password?(:login=>'[email protected]', :password=>'0123456789').must_equal true | |
+ app.rodauth.valid_login_and_password?(:login=>'[email protected]', :password=>'012345678').must_equal false | |
+ app.rodauth.valid_login_and_password?(:login=>'[email protected]', :password=>'0123456789').must_equal false | |
+ | |
+ app.rodauth.valid_login_and_password?(:account_login=>'[email protected]', :password=>'0123456789').must_equal true | |
+ app.rodauth.valid_login_and_password?(:account_login=>'[email protected]', :password=>'012345678').must_equal false | |
+ | |
+ proc do | |
+ app.rodauth.valid_login_and_password?(:account_login=>'[email protected]', :password=>'0123456789') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ app.rodauth.login(:account_login=>'[email protected]', :password=>'0123456789').must_equal DB[:accounts].get(:id) | |
+ | |
+ proc do | |
+ app.rodauth.login(:login=>'[email protected]', :password=>'012345678') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ proc do | |
+ app.rodauth.login(:login=>'[email protected]', :password=>'0123456789') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ app.rodauth.login(:account_login=>'[email protected]', :password=>'0123456789').must_equal DB[:accounts].get(:id) | |
+ | |
+ proc do | |
+ app.rodauth.login(:account_login=>'[email protected]', :password=>'012345678') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ end | |
end | |
diff --git a/spec/remember_spec.rb b/spec/remember_spec.rb | |
index 2f23424..1a64d32 100644 | |
--- a/spec/remember_spec.rb | |
+++ b/spec/remember_spec.rb | |
@@ -559,4 +559,62 @@ describe 'Rodauth remember feature' do | |
json_request.must_equal [200, [3]] | |
end | |
end | |
+ | |
+ it "should support remember token management via internal requests" do | |
+ key = nil | |
+ rodauth do | |
+ enable :login, :logout, :remember, :internal_request | |
+ hmac_secret '123' | |
+ end | |
+ roda do |r| | |
+ r.rodauth | |
+ r.get 'setup' do | |
+ key = rodauth.class.remember_setup(:account_login=>'[email protected]') | |
+ ::Rack::Utils.set_cookie_header!(response.headers, '_remember', :value=>key) | |
+ '' | |
+ end | |
+ r.get 'load' do | |
+ rodauth.load_memory | |
+ r.redirect '/' | |
+ end | |
+ r.get 'loadpage' do | |
+ rodauth.load_memory | |
+ '' | |
+ end | |
+ r.root do | |
+ if rodauth.logged_in? | |
+ if rodauth.logged_in_via_remember_key? | |
+ view :content=>"Logged In via Remember" | |
+ else | |
+ view :content=>"Logged In Normally" | |
+ end | |
+ else | |
+ view :content=>"Not Logged In" | |
+ end | |
+ end | |
+ end | |
+ | |
+ visit '/setup' | |
+ page.body.must_equal '' | |
+ | |
+ app.rodauth.account_id_for_remember_key(:remember=>key).must_equal DB[:accounts].get(:id) | |
+ app.rodauth.account_id_for_remember_key(:remember=>key[0...-1]).must_be_nil | |
+ | |
+ visit '/' | |
+ page.body.must_include 'Not Logged In' | |
+ | |
+ visit '/load' | |
+ page.body.must_include 'Logged In via Remember' | |
+ | |
+ logout | |
+ | |
+ visit '/setup' | |
+ page.body.must_equal '' | |
+ | |
+ app.rodauth.remember_disable(:account_login=>'[email protected]').must_be_nil | |
+ app.rodauth.account_id_for_remember_key(:remember=>key).must_be_nil | |
+ | |
+ visit '/load' | |
+ page.body.must_include 'Not Logged In' | |
+ end | |
end | |
diff --git a/spec/reset_password_spec.rb b/spec/reset_password_spec.rb | |
index 8ba9daa..375f571 100644 | |
--- a/spec/reset_password_spec.rb | |
+++ b/spec/reset_password_spec.rb | |
@@ -289,4 +289,82 @@ describe 'Rodauth reset_password feature' do | |
json_login(:pass=>'0123456') | |
end | |
end | |
+ | |
+ it "should support requesting password resets using an internal request" do | |
+ rodauth do | |
+ enable :login, :logout, :reset_password, :internal_request | |
+ reset_password_email_last_sent_column nil | |
+ end | |
+ roda do |r| | |
+ r.rodauth | |
+ r.root{view :content=>(rodauth.logged_in? ? "Logged In" : "Not Logged")} | |
+ end | |
+ | |
+ proc do | |
+ app.rodauth.reset_password_request(:login=>'[email protected]') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ proc do | |
+ app.rodauth.reset_password_request(:account_login=>'[email protected]') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ proc do | |
+ app.rodauth.reset_password(:account_login=>'[email protected]') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ proc do | |
+ app.rodauth.reset_password(:account_login=>'[email protected]') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ app.rodauth.reset_password_request(:login=>'[email protected]').must_be_nil | |
+ link = email_link(/(\/reset-password\?key=.+)$/) | |
+ | |
+ app.rodauth.reset_password_request(:account_login=>'[email protected]').must_be_nil | |
+ link2 = email_link(/(\/reset-password\?key=.+)$/) | |
+ link2.must_equal link | |
+ | |
+ visit link | |
+ fill_in 'Password', :with=>'0123456' | |
+ fill_in 'Confirm Password', :with=>'0123456' | |
+ click_button 'Reset Password' | |
+ page.find('#notice_flash').text.must_equal "Your password has been reset" | |
+ | |
+ login(:pass=>'0123456') | |
+ page.body.must_include "Logged In" | |
+ | |
+ logout | |
+ | |
+ app.rodauth.reset_password_request(:account_login=>'[email protected]').must_be_nil | |
+ email_link(/(\/reset-password\?key=.+)$/) | |
+ app.rodauth.reset_password(:account_login=>'[email protected]', :password=>'01234567').must_be_nil | |
+ | |
+ login(:pass=>'01234567') | |
+ page.body.must_include "Logged In" | |
+ | |
+ logout | |
+ | |
+ app.rodauth.reset_password_request(:login=>'[email protected]').must_be_nil | |
+ link = email_link(/(\/reset-password\?key=.+)$/) | |
+ | |
+ app.rodauth.reset_password(:account_login=>'[email protected]', :password=>'012345678').must_be_nil | |
+ | |
+ visit link | |
+ page.find('#error_flash').text.must_equal "There was an error resetting your password: invalid or expired password reset key" | |
+ | |
+ login(:pass=>'012345678') | |
+ page.body.must_include "Logged In" | |
+ | |
+ app.rodauth.reset_password_request(:login=>'[email protected]').must_be_nil | |
+ link = email_link(/(\/reset-password\?key=.+)$/) | |
+ key = link.split('=').last | |
+ | |
+ proc do | |
+ app.rodauth.reset_password(:reset_password_key=>key[0...-1], :password=>'0123456789').must_be_nil | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ app.rodauth.reset_password(:reset_password_key=>key, :password=>'0123456789').must_be_nil | |
+ | |
+ login(:pass=>'0123456789') | |
+ page.body.must_include "Logged In" | |
+ end | |
end | |
diff --git a/spec/rodauth_spec.rb b/spec/rodauth_spec.rb | |
index 94df9e5..2c65813 100644 | |
--- a/spec/rodauth_spec.rb | |
+++ b/spec/rodauth_spec.rb | |
@@ -413,6 +413,26 @@ describe 'Rodauth' do | |
Rodauth::FEATURES.delete(:foo) | |
end | |
+ it "should support features with multiple class_methods blocks" do | |
+ require "rodauth" | |
+ Rodauth::Feature.define(:foo) do | |
+ class_methods{def a; 1; end} | |
+ class_methods{def b; 2 + a; end} | |
+ end | |
+ | |
+ rodauth do | |
+ enable :foo | |
+ end | |
+ roda do |r| | |
+ self.class.rodauth.b.to_s | |
+ end | |
+ | |
+ visit '/' | |
+ page.body.must_equal '3' | |
+ | |
+ Rodauth::FEATURES.delete(:foo) | |
+ end | |
+ | |
it "should support auth_class_eval for evaluation inside Auth class" do | |
rodauth do | |
enable :login | |
@@ -591,4 +611,117 @@ describe 'Rodauth' do | |
visit '/login' | |
hooks.must_equal [:before_around, :before, :after_around] | |
end | |
+ | |
+ { | |
+ 'should allow different configuerations for internal requests'=>true, | |
+ 'should allow use of internal_request? to determine whether this is an internal request'=>false | |
+ }.each do |desc, use_internal_request_predicate| | |
+ it desc do | |
+ rodauth do | |
+ enable :login, :logout, :create_account, :internal_request | |
+ require_login_confirmation? false | |
+ require_password_confirmation? false | |
+ | |
+ if use_internal_request_predicate | |
+ login_minimum_length{internal_request? ? 9 : 15} | |
+ password_minimum_length{internal_request? ? 3 : super()} | |
+ else | |
+ login_minimum_length 15 | |
+ | |
+ internal_request_configuration do | |
+ login_minimum_length 9 | |
+ end | |
+ | |
+ internal_request_configuration do | |
+ password_minimum_length 3 | |
+ end | |
+ end | |
+ end | |
+ roda do |r| | |
+ r.rodauth | |
+ view :content=>"" | |
+ end | |
+ | |
+ visit '/create-account' | |
+ fill_in 'Login', :with=>'[email protected]' | |
+ fill_in 'Password', :with=>'012' | |
+ click_button 'Create Account' | |
+ page.html.must_include("invalid login, minimum 15 characters") | |
+ page.find('#error_flash').text.must_equal "There was an error creating your account" | |
+ | |
+ fill_in 'Login', :with=>'[email protected]' | |
+ fill_in 'Password', :with=>'012' | |
+ click_button 'Create Account' | |
+ page.html.must_include("invalid password, does not meet requirements (minimum 6 characters)") | |
+ page.find('#error_flash').text.must_equal "There was an error creating your account" | |
+ | |
+ fill_in 'Password', :with=>'123456' | |
+ click_button 'Create Account' | |
+ page.find('#notice_flash').text.must_equal "Your account has been created" | |
+ | |
+ login(:login=>'[email protected]', :pass=>'123456') | |
+ page.find('#notice_flash').text.must_equal 'You have been logged in' | |
+ logout | |
+ | |
+ app.rodauth.create_account(:login=>'[email protected]', :password=>'012').must_be_nil | |
+ | |
+ proc do | |
+ app.rodauth.create_account(:login=>'[email protected]', :password=>'12') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ proc do | |
+ app.rodauth.create_account(:login=>'[email protected]', :password=>'012') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ login(:login=>'[email protected]', :pass=>'012') | |
+ page.find('#notice_flash').text.must_equal 'You have been logged in' | |
+ end | |
+ end | |
+ | |
+ it "should allow custom options when creating internal requests" do | |
+ rodauth do | |
+ enable :login, :logout, :create_account, :change_login, :internal_request | |
+ before_create_account_route do | |
+ params[login_param] += request.env[:at] + session[:domain] | |
+ end | |
+ before_change_login_route do | |
+ params[login_param] += authenticated_by.first | |
+ end | |
+ end | |
+ roda do |r| | |
+ r.rodauth | |
+ view :content=>"" | |
+ end | |
+ | |
+ app.rodauth.create_account(:login=>'foo', :password=>'0123456789', :env=>{:at=>'@'}, :session=>{:domain=>'g.com'}).must_be_nil | |
+ | |
+ login(:login=>'[email protected]') | |
+ page.find('#notice_flash').text.must_equal 'You have been logged in' | |
+ logout | |
+ | |
+ app.rodauth.change_login(:account_id=>DB[:accounts].where(:email=>'[email protected]').get(:id), :login=>'foo@h.', :authenticated_by=>['com']).must_be_nil | |
+ | |
+ login(:login=>'[email protected]') | |
+ page.find('#notice_flash').text.must_equal 'You have been logged in' | |
+ end | |
+ | |
+ it "should warn for invalid options for internal requests" do | |
+ warning = nil | |
+ rodauth do | |
+ enable :login, :logout, :create_account, :internal_request | |
+ auth_class_eval do | |
+ define_singleton_method(:warn){|*a| warning = a} | |
+ end | |
+ end | |
+ roda do |r| | |
+ r.rodauth | |
+ view :content=>"" | |
+ end | |
+ | |
+ app.rodauth.create_account(:login=>'[email protected]', :password=>'0123456789', :banana=>:pear).must_be_nil | |
+ warning.must_equal ["unhandled options passed to create_account: {:banana=>:pear}"] | |
+ | |
+ login(:login=>'[email protected]') | |
+ page.find('#notice_flash').text.must_equal 'You have been logged in' | |
+ end | |
end | |
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb | |
index 09404d0..abc0678 100644 | |
--- a/spec/spec_helper.rb | |
+++ b/spec/spec_helper.rb | |
@@ -119,7 +119,7 @@ DB = Sequel.connect(db_url, :identifier_mangling=>false) | |
DB.extension :freeze_datasets, :date_arithmetic | |
puts "using #{DB.database_type}" | |
-#DB.loggers << Logger.new($stdout) | |
+DB.loggers << Logger.new($stdout) if ENV['LOG_SQL'] | |
if DB.adapter_scheme == :jdbc | |
case DB.database_type | |
when :postgres | |
diff --git a/spec/two_factor_spec.rb b/spec/two_factor_spec.rb | |
index eaa2cac..19dbc8b 100644 | |
--- a/spec/two_factor_spec.rb | |
+++ b/spec/two_factor_spec.rb | |
@@ -1641,6 +1641,250 @@ describe 'Rodauth OTP feature' do | |
page.html.must_include 'With OTP' | |
end | |
+ it "should allow using otp via internal requests" do | |
+ rodauth do | |
+ enable :login, :logout, :otp, :internal_request | |
+ hmac_secret '123' | |
+ end | |
+ roda do |r| | |
+ r.rodauth | |
+ r.redirect '/login' unless rodauth.logged_in? | |
+ r.redirect '/otp-setup' unless rodauth.two_factor_authentication_setup? | |
+ r.redirect '/otp-auth' unless rodauth.two_factor_authenticated? | |
+ view :content=>"" | |
+ end | |
+ | |
+ secret, raw_secret = app.rodauth.otp_setup_params(:account_login=>'[email protected]') | |
+ totp = ROTP::TOTP.new(secret) | |
+ | |
+ proc do | |
+ app.rodauth.otp_setup(:account_login=>'[email protected]', :otp_setup=>secret[0...-1], :otp_setup_raw=>raw_secret, :otp_auth=>totp.now) | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ proc do | |
+ app.rodauth.otp_setup(:account_login=>'[email protected]', :otp_setup=>secret, :otp_setup_raw=>raw_secret[0...-1], :otp_auth=>totp.now) | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ proc do | |
+ app.rodauth.otp_setup(:account_login=>'[email protected]', :otp_setup=>secret, :otp_setup_raw=>raw_secret, :otp_auth=>totp.now[0...-1]) | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ app.rodauth.otp_setup(:account_login=>'[email protected]', :otp_setup=>secret, :otp_setup_raw=>raw_secret, :otp_auth=>totp.now).must_be_nil | |
+ reset_otp_last_use | |
+ | |
+ proc do | |
+ app.rodauth.otp_setup_params(:account_login=>'[email protected]') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ proc do | |
+ app.rodauth.otp_setup(:account_login=>'[email protected]', :otp_setup=>secret, :otp_setup_raw=>raw_secret, :otp_auth=>totp.now) | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ login | |
+ fill_in 'Authentication Code', :with=>totp.now | |
+ click_button 'Authenticate Using TOTP' | |
+ page.find('#notice_flash').text.must_equal 'You have been multifactor authenticated' | |
+ reset_otp_last_use | |
+ | |
+ proc do | |
+ app.rodauth.otp_auth(:account_login=>'[email protected]', :otp_auth=>totp.now[0...-1]) | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ app.rodauth.otp_auth(:account_login=>'[email protected]', :otp_auth=>totp.now).must_be_nil | |
+ reset_otp_last_use | |
+ | |
+ app.rodauth.valid_otp_auth?(:account_login=>'[email protected]', :otp_auth=>totp.now[0...-1]).must_equal false | |
+ reset_otp_last_use | |
+ | |
+ app.rodauth.valid_otp_auth?(:account_login=>'[email protected]', :otp_auth=>totp.now).must_equal true | |
+ reset_otp_last_use | |
+ | |
+ app.rodauth.otp_disable(:account_login=>'[email protected]').must_be_nil | |
+ | |
+ app.rodauth.valid_otp_auth?(:account_login=>'[email protected]', :otp_auth=>totp.now[0...-1]).must_equal false | |
+ | |
+ proc do | |
+ app.rodauth.otp_disable(:account_login=>'[email protected]') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ end | |
+ | |
+ it "should allow using otp via internal requests without hmac" do | |
+ rodauth do | |
+ enable :login, :logout, :otp, :internal_request | |
+ end | |
+ roda do |r| | |
+ end | |
+ | |
+ secret, raw_secret = app.rodauth.otp_setup_params(:account_login=>'[email protected]') | |
+ raw_secret.must_equal secret | |
+ totp = ROTP::TOTP.new(secret) | |
+ | |
+ proc do | |
+ app.rodauth.otp_setup(:account_login=>'[email protected]', :otp_setup=>secret[0...-1], :otp_auth=>totp.now) | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ proc do | |
+ app.rodauth.otp_setup(:account_login=>'[email protected]', :otp_setup=>secret, :otp_auth=>totp.now[0...-1]) | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ app.rodauth.otp_setup(:account_login=>'[email protected]', :otp_setup=>secret, :otp_auth=>totp.now).must_be_nil | |
+ reset_otp_last_use | |
+ | |
+ app.rodauth.valid_otp_auth?(:account_login=>'[email protected]', :otp_auth=>totp.now).must_equal true | |
+ reset_otp_last_use | |
+ end | |
+ | |
+ it "should allow using recovery codes via internal requests" do | |
+ rodauth do | |
+ enable :login, :logout, :recovery_codes, :internal_request | |
+ recovery_codes_primary? false | |
+ end | |
+ roda do |r| | |
+ r.rodauth | |
+ r.redirect '/login' unless rodauth.logged_in? | |
+ rodauth.require_two_factor_authenticated | |
+ view :content=>"" | |
+ end | |
+ | |
+ app.rodauth.recovery_codes(:account_login=>'[email protected]').must_equal [] | |
+ | |
+ recovery_codes = app.rodauth.recovery_codes(:account_login=>'[email protected]', :add_recovery_codes=>'1') | |
+ recovery_codes.length.must_equal 16 | |
+ | |
+ proc do | |
+ app.rodauth.recovery_auth(:account_login=>'[email protected]', :recovery_codes=>'foo') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ app.rodauth.recovery_auth(:account_login=>'[email protected]', :recovery_codes=>recovery_codes.shift).must_be_nil | |
+ | |
+ app.rodauth.valid_recovery_auth?(:account_login=>'[email protected]', :recovery_codes=>'foo').must_equal false | |
+ app.rodauth.valid_recovery_auth?(:account_login=>'[email protected]', :recovery_codes=>recovery_codes.shift).must_equal true | |
+ | |
+ login | |
+ | |
+ fill_in 'Recovery Code', :with=>recovery_codes.shift | |
+ click_button 'Authenticate via Recovery Code' | |
+ page.find('#notice_flash').text.must_equal 'You have been multifactor authenticated' | |
+ | |
+ recovery_codes2 = app.rodauth.recovery_codes(:account_login=>'[email protected]') | |
+ recovery_codes2.sort.must_equal recovery_codes.sort | |
+ | |
+ recovery_codes3 = app.rodauth.recovery_codes(:account_login=>'[email protected]', :add_recovery_codes=>'1') | |
+ recovery_codes3.length.must_equal 16 | |
+ (recovery_codes & recovery_codes3).length.must_equal 13 | |
+ end | |
+ | |
+ it "should allow using sms codes via internal requests" do | |
+ sms_message = nil | |
+ rodauth do | |
+ enable :login, :logout, :sms_codes, :internal_request | |
+ sms_send do |phone, msg| | |
+ sms_message = msg | |
+ end | |
+ domain 'example.com' | |
+ end | |
+ roda do |r| | |
+ r.rodauth | |
+ rodauth.require_two_factor_authenticated | |
+ view :content=>"" | |
+ end | |
+ | |
+ app.rodauth.sms_setup(:account_login=>'[email protected]', :sms_phone=>'1112223333').must_be_nil | |
+ sms_message.must_match(/\ASMS confirmation code for example\.com is \d{12}\z/) | |
+ sms_code = sms_message[/\d{12}\z/] | |
+ | |
+ login | |
+ fill_in 'SMS Code', :with=>sms_code | |
+ click_button 'Confirm SMS Backup Number' | |
+ page.find('#notice_flash').text.must_equal 'SMS authentication has been setup' | |
+ logout | |
+ | |
+ proc do | |
+ app.rodauth.sms_setup(:account_login=>'[email protected]', :sms_phone=>'1112224444') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ login | |
+ page.title.must_equal 'Send SMS Code' | |
+ click_button 'Send SMS Code' | |
+ sms_message.must_match(/\ASMS authentication code for example\.com is \d{6}\z/) | |
+ sms_code = sms_message[/\d{6}\z/] | |
+ | |
+ fill_in 'SMS Code', :with=>sms_code | |
+ click_button 'Authenticate via SMS Code' | |
+ page.find('#notice_flash').text.must_equal 'You have been multifactor authenticated' | |
+ logout | |
+ | |
+ app.rodauth.sms_disable(:account_login=>'[email protected]').must_be_nil | |
+ | |
+ proc do | |
+ app.rodauth.sms_disable(:account_login=>'[email protected]') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ login | |
+ fill_in 'Password', :with=>'0123456789' | |
+ fill_in 'Phone Number', :with=>'(123) 456-7890' | |
+ click_button 'Setup SMS Backup Number' | |
+ page.find('#notice_flash').text.must_equal 'SMS authentication needs confirmation' | |
+ sms_message.must_match(/\ASMS confirmation code for example\.com is \d{12}\z/) | |
+ sms_code = sms_message[/\d{12}\z/] | |
+ logout | |
+ | |
+ app.rodauth.sms_confirm(:account_login=>'[email protected]', :sms_code=>sms_code).must_be_nil | |
+ | |
+ proc do | |
+ app.rodauth.sms_confirm(:account_login=>'[email protected]', :sms_code=>sms_code) | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ app.rodauth.sms_request(:account_login=>'[email protected]').must_be_nil | |
+ sms_message.must_match(/\ASMS authentication code for example\.com is \d{6}\z/) | |
+ sms_code = sms_message[/\d{6}\z/] | |
+ app.rodauth.sms_auth(:account_login=>'[email protected]', :sms_code=>sms_code).must_be_nil | |
+ | |
+ login | |
+ page.title.must_equal 'Send SMS Code' | |
+ click_button 'Send SMS Code' | |
+ sms_message.must_match(/\ASMS authentication code for example\.com is \d{6}\z/) | |
+ sms_code = sms_message[/\d{6}\z/] | |
+ logout | |
+ | |
+ app.rodauth.valid_sms_auth?(:account_login=>'[email protected]', :sms_code=>sms_code).must_equal true | |
+ app.rodauth.valid_sms_auth?(:account_login=>'[email protected]', :sms_code=>sms_code).must_equal false | |
+ end | |
+ | |
+ it "should allow removing all multifactor authentication via internal requests" do | |
+ sms_message = nil | |
+ rodauth do | |
+ enable :otp, :sms_codes, :recovery_codes, :internal_request | |
+ sms_send do |phone, msg| | |
+ sms_message = msg | |
+ end | |
+ domain 'example.com' | |
+ end | |
+ roda do |r| | |
+ end | |
+ | |
+ secret, raw_secret = app.rodauth.otp_setup_params(:account_login=>'[email protected]') | |
+ totp = ROTP::TOTP.new(secret) | |
+ app.rodauth.otp_setup(:account_login=>'[email protected]', :otp_setup=>secret, :otp_setup_raw=>raw_secret, :otp_auth=>totp.now).must_be_nil | |
+ | |
+ app.rodauth.sms_setup(:account_login=>'[email protected]', :sms_phone=>'1112223333').must_be_nil | |
+ sms_message.must_match(/\ASMS confirmation code for example\.com is \d{12}\z/) | |
+ sms_code = sms_message[/\d{12}\z/] | |
+ app.rodauth.sms_confirm(:account_login=>'[email protected]', :sms_code=>sms_code).must_be_nil | |
+ app.rodauth.sms_request(:account_login=>'[email protected]').must_be_nil | |
+ sms_message.must_match(/\ASMS authentication code for example\.com is \d{6}\z/) | |
+ sms_code = sms_message[/\d{6}\z/] | |
+ app.rodauth.sms_auth(:account_login=>'[email protected]', :sms_code=>sms_code).must_be_nil | |
+ | |
+ recovery_codes = app.rodauth.recovery_codes(:account_login=>'[email protected]', :add_recovery_codes=>'1') | |
+ app.rodauth.recovery_auth(:account_login=>'[email protected]', :recovery_codes=>recovery_codes.shift).must_be_nil | |
+ | |
+ app.rodauth.two_factor_disable(:account_login=>'[email protected]').must_be_nil | |
+ [:account_otp_keys, :account_recovery_codes, :account_sms_codes].each do |t| | |
+ DB[t].count.must_equal 0 | |
+ end | |
+ end | |
+ | |
begin | |
require 'webauthn/fake_client' | |
rescue LoadError | |
diff --git a/spec/verify_account_spec.rb b/spec/verify_account_spec.rb | |
index cedae45..fa0b84f 100644 | |
--- a/spec/verify_account_spec.rb | |
+++ b/spec/verify_account_spec.rb | |
@@ -312,4 +312,96 @@ describe 'Rodauth verify_account feature' do | |
json_login(:login=>'[email protected]') | |
end | |
end | |
+ | |
+ it "should allow verifying accounts using internal requests" do | |
+ rodauth do | |
+ enable :login, :logout, :verify_account, :internal_request, :change_password | |
+ verify_account_email_last_sent_column nil | |
+ end | |
+ roda do |r| | |
+ r.rodauth | |
+ r.root{view :content=>rodauth.logged_in? ? "Logged In" : "Not Logged"} | |
+ end | |
+ | |
+ proc do | |
+ app.rodauth.verify_account_resend(:login=>'[email protected]') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ proc do | |
+ app.rodauth.verify_account(:account_login=>'[email protected]') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ proc do | |
+ app.rodauth.verify_account_resend(:account_login=>'[email protected]') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ proc do | |
+ app.rodauth.verify_account(:account_login=>'[email protected]') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ visit '/create-account' | |
+ fill_in 'Login', :with=>'[email protected]' | |
+ click_button 'Create Account' | |
+ page.find('#notice_flash').text.must_equal "An email has been sent to you with a link to verify your account" | |
+ page.current_path.must_equal '/' | |
+ link = email_link(/(\/verify-account\?key=.+)$/, '[email protected]') | |
+ | |
+ app.rodauth.verify_account_resend(:account_login=>'[email protected]').must_be_nil | |
+ link2 = email_link(/(\/verify-account\?key=.+)$/, '[email protected]') | |
+ link2.must_equal link | |
+ | |
+ visit link | |
+ fill_in 'Password', :with=>'0123456789' | |
+ fill_in 'Confirm Password', :with=>'0123456789' | |
+ click_button 'Verify Account' | |
+ page.find('#notice_flash').text.must_equal "Your account has been verified" | |
+ page.body.must_include 'Logged In' | |
+ logout | |
+ | |
+ login(:login=>'[email protected]') | |
+ page.body.must_include 'Logged In' | |
+ logout | |
+ | |
+ visit '/create-account' | |
+ fill_in 'Login', :with=>'[email protected]' | |
+ click_button 'Create Account' | |
+ page.find('#notice_flash').text.must_equal "An email has been sent to you with a link to verify your account" | |
+ page.current_path.must_equal '/' | |
+ link = email_link(/(\/verify-account\?key=.+)$/, '[email protected]') | |
+ | |
+ app.rodauth.verify_account_resend(:login=>'[email protected]').must_be_nil | |
+ link2 = email_link(/(\/verify-account\?key=.+)$/, '[email protected]') | |
+ link2.must_equal link | |
+ | |
+ app.rodauth.verify_account(:account_login=>'[email protected]', :password=>'0123456789').must_be_nil | |
+ | |
+ login(:login=>'[email protected]') | |
+ page.body.must_include 'Logged In' | |
+ logout | |
+ | |
+ app.rodauth.create_account(:login=>'[email protected]').must_be_nil | |
+ link = email_link(/(\/verify-account\?key=.+)$/, '[email protected]') | |
+ | |
+ app.rodauth.verify_account_resend(:login=>'[email protected]').must_be_nil | |
+ link2 = email_link(/(\/verify-account\?key=.+)$/, '[email protected]') | |
+ link2.must_equal link | |
+ | |
+ app.rodauth.verify_account(:account_login=>'[email protected]', :password=>'0123456789').must_be_nil | |
+ | |
+ login(:login=>'[email protected]') | |
+ page.body.must_include 'Logged In' | |
+ | |
+ app.rodauth.create_account(:login=>'[email protected]').must_be_nil | |
+ link = email_link(/(\/verify-account\?key=.+)$/, '[email protected]') | |
+ key = link.split('=').last | |
+ | |
+ proc do | |
+ app.rodauth.verify_account(:verify_account_key=>key[0...-1], :password=>'0123456789') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ app.rodauth.verify_account(:verify_account_key=>key, :password=>'0123456789').must_be_nil | |
+ | |
+ login(:login=>'[email protected]') | |
+ page.body.must_include 'Logged In' | |
+ end | |
end | |
diff --git a/spec/verify_login_change_spec.rb b/spec/verify_login_change_spec.rb | |
index a9e5050..0d78f5d 100644 | |
--- a/spec/verify_login_change_spec.rb | |
+++ b/spec/verify_login_change_spec.rb | |
@@ -232,4 +232,53 @@ describe 'Rodauth verify_login_change feature' do | |
json_login(:login=>'[email protected]') | |
end | |
end | |
+ | |
+ it "should support verifying login changes using internal requests" do | |
+ rodauth do | |
+ enable :login, :logout, :verify_login_change, :internal_request | |
+ end | |
+ roda do |r| | |
+ r.rodauth | |
+ r.root{view :content=>rodauth.logged_in? ? "Logged In" : "Not Logged"} | |
+ end | |
+ | |
+ proc do | |
+ app.rodauth.verify_login_change(:account_login=>'[email protected]') | |
+ end.must_raise Rodauth::InternalRequestError | |
+ | |
+ login | |
+ | |
+ visit '/change-login' | |
+ fill_in 'Login', :with=>'[email protected]' | |
+ fill_in 'Password', :with=>'0123456789' | |
+ click_button 'Change Login' | |
+ link = email_link(/(\/verify-login-change\?key=.+)$/, '[email protected]') | |
+ | |
+ app.rodauth.verify_login_change(:account_login=>'[email protected]').must_be_nil | |
+ | |
+ visit link | |
+ page.find('#error_flash').text.must_equal "There was an error verifying your login change: invalid verify login change key" | |
+ | |
+ login(:login=>'[email protected]') | |
+ page.body.must_include('Logged In') | |
+ logout | |
+ | |
+ app.rodauth.change_login(:account_login=>'[email protected]', :login=>'[email protected]').must_be_nil | |
+ link = email_link(/(\/verify-login-change\?key=.+)$/, '[email protected]') | |
+ app.rodauth.verify_login_change(:account_login=>'[email protected]').must_be_nil | |
+ | |
+ visit link | |
+ page.find('#error_flash').text.must_equal "There was an error verifying your login change: invalid verify login change key" | |
+ | |
+ login(:login=>'[email protected]') | |
+ page.body.must_include('Logged In') | |
+ logout | |
+ | |
+ app.rodauth.change_login(:account_login=>'[email protected]', :login=>'[email protected]').must_be_nil | |
+ key = email_link(/(\/verify-login-change\?key=.+)$/, '[email protected]').split('=').last | |
+ app.rodauth.verify_login_change(:verify_login_change_key=>key).must_be_nil | |
+ | |
+ login(:login=>'[email protected]') | |
+ page.body.must_include('Logged In') | |
+ end | |
end | |
diff --git a/www/pages/documentation.erb b/www/pages/documentation.erb | |
index 7c5cbb4..ec51329 100644 | |
--- a/www/pages/documentation.erb | |
+++ b/www/pages/documentation.erb | |
@@ -27,6 +27,7 @@ | |
<li><a href="rdoc/files/doc/disallow_password_reuse_rdoc.html">Disallow Password Reuse</a>: Disallows setting password to the same string as previous passwords.</li> | |
<li><a href="rdoc/files/doc/email_auth_rdoc.html">Email Authentication</a>: Allows login via a link sent via email.</li> | |
<li><a href="rdoc/files/doc/http_basic_auth_rdoc.html">HTTP Basic Auth</a>: Allows HTTP basic authentication.</li> | |
+ <li><a href="rdoc/files/doc/internal_request_rdoc.html">Internal Request</a>: Allows interacting with Rodauth by calling methods.</li> | |
<li><a href="rdoc/files/doc/json_rdoc.html">JSON</a>: Adds JSON API support for all other features.</li> | |
<li><a href="rdoc/files/doc/jwt_rdoc.html">JWT</a>: Adds JSON Web Token support for all other features.</li> | |
<li><a href="rdoc/files/doc/jwt_cors_rdoc.html">JWT CORS</a>: Supports Cross-Origin Resource Sharing in the JSON API.</li> | |
diff --git a/www/pages/why.erb b/www/pages/why.erb | |
index 07d82d8..ed19627 100644 | |
--- a/www/pages/why.erb | |
+++ b/www/pages/why.erb | |
@@ -80,6 +80,7 @@ | |
<li>JWT (JSON Web Token support for all other features)</li> | |
<li>JWT CORS (Cross-Origin Resource Sharing)</li> | |
<li>JWT Refresh (Access & refresh tokens)</li> | |
+ <li>Internal Request (Interact with Rodauth via methods)</li> | |
</ul> | |
<p style="margin-top: 20px;">You can learn more about these features by reviewing <a href="documentation.html">Rodauth's documentation</a>.</p> | |
-- | |
2.31.1 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment