# frozen_string_literal: true require 'test_helper' # was the web request successful? # was the user redirected to the right page? # was the user successfully authenticated? # was the correct object stored in the response? # was the appropriate message delivered in the json payload? class DeviseJwtAuth::PasswordsControllerTest < ActionController::TestCase describe DeviseJwtAuth::PasswordsController do describe 'Password reset' do before do @resource = create(:user, :confirmed) @redirect_url = 'http://ng-token-auth.dev' end describe 'not email should return 401' do before do @auth_headers = @resource.create_named_token_pair @new_password = Faker::Internet.password post :create, params: { redirect_url: @redirect_url } @data = JSON.parse(response.body) end test 'response should fail' do assert_equal 401, response.status end test 'error message should be returned' do assert @data['errors'] assert_equal @data['errors'], [I18n.t('devise_jwt_auth.passwords.missing_email')] end end describe 'not redirect_url should return 401' do before do @auth_headers = @resource.create_named_token_pair @new_password = Faker::Internet.password end describe 'for create' do before do post :create, params: { email: 'chester@cheet.ah' } @data = JSON.parse(response.body) end test 'response should fail' do assert_equal 401, response.status end test 'error message should be returned' do assert @data['errors'] assert_equal( @data['errors'], [I18n.t('devise_jwt_auth.passwords.missing_redirect_url')] ) end end describe 'for edit' do before do get_reset_token get :edit, params: { reset_password_token: @mail_reset_token } @data = JSON.parse(response.body) end test 'response should fail' do assert_equal 401, response.status end test 'response should not have access token' do assert_nil @data[DeviseJwtAuth.access_token_name] end test 'error message should be returned' do assert @data['errors'] assert_equal( @data['errors'], [I18n.t('devise_jwt_auth.passwords.missing_redirect_url')] ) end end end describe 'request password reset' do describe 'unknown user should return 404' do before do post :create, params: { email: 'chester@cheet.ah', redirect_url: @redirect_url } @data = JSON.parse(response.body) end test 'unknown user should return 404' do assert_equal 404, response.status end test 'response should not have access token' do assert_nil @data[DeviseJwtAuth.access_token_name] end test 'errors should be returned' do assert @data['errors'] assert_equal @data['errors'], [I18n.t('devise_jwt_auth.passwords.user_not_found', email: 'chester@cheet.ah')] end test 'response should not have refresh cookie' do assert_nil response.cookies[DeviseJwtAuth.refresh_token_name] end end describe 'successfully requested password reset' do before do post :create, params: { email: @resource.email, redirect_url: @redirect_url } @data = JSON.parse(response.body) # TODO: check for __NO__ access and refresh tokens and also # token version bump that invalidates old tokens end test 'response should not contain extra data' do assert_nil @data['data'] end end describe 'case-sensitive email' do before do post :create, params: { email: @resource.email, redirect_url: @redirect_url } @mail = ActionMailer::Base.deliveries.last @resource.reload @data = JSON.parse(response.body) @mail_config_name = CGI.unescape(@mail.body.match(/config=([^&]*)&/)[1]) @mail_redirect_url = CGI.unescape(@mail.body.match(/redirect_url=([^&]*)&/)[1]) @mail_reset_token = @mail.body.match(/reset_password_token=(.*)"/)[1] end test 'response should return success status' do assert_equal 200, response.status end test 'response should not have access token' do assert_nil @data[DeviseJwtAuth.access_token_name] end test 'response should contains message' do assert_equal \ @data['message'], I18n.t('devise_jwt_auth.passwords.sended', email: @resource.email) end test 'response should not have a refresh cookie' do assert_nil response.cookies[DeviseJwtAuth.refresh_token_name] end test 'action should send an email' do assert @mail end test 'the email should be addressed to the user' do assert_equal @mail.to.first, @resource.email end test 'the email body should contain a link with redirect url as a query param' do assert_equal @redirect_url, @mail_redirect_url end test 'the client config name should fall back to "default"' do assert_equal 'default', @mail_config_name end test 'the email body should contain a link with reset token as a query param' do user = User.reset_password_by_token(reset_password_token: @mail_reset_token) assert_equal user.id, @resource.id end describe 'password reset link failure' do test 'response should return 404' do assert_raises(ActionController::RoutingError) do get :edit, params: { reset_password_token: 'bogus', redirect_url: @mail_redirect_url } end end end describe 'password reset link success' do before do get :edit, params: { reset_password_token: @mail_reset_token, redirect_url: @mail_redirect_url } @resource.reload raw_qs = response.location.split('?')[1] @qs = Rack::Utils.parse_nested_query(raw_qs) @access_token = @qs[DeviseJwtAuth.access_token_name] @reset_password = @qs['reset_password'] @refresh_token = response.cookies[DeviseJwtAuth.refresh_token_name] end test 'response should have success redirect status' do assert_equal 302, response.status end test 'response should contain auth params' do assert @access_token assert @reset_password assert @refresh_token end test 'access and refresh tokens should be valid' do payload = DeviseJwtAuth::TokenFactory.decode_access_token(@access_token) assert payload['sub'] payload = DeviseJwtAuth::TokenFactory.decode_refresh_token(@refresh_token) assert payload['sub'] end end end describe 'case-insensitive email' do before do @resource_class = User @request_params = { email: @resource.email.upcase, redirect_url: @redirect_url } end test 'response should return success status if configured' do @resource_class.case_insensitive_keys = [:email] post :create, params: @request_params assert_equal 200, response.status end test 'response should return failure status if not configured' do @resource_class.case_insensitive_keys = [] post :create, params: @request_params assert_equal 404, response.status end end describe 'Checking reset_password_token' do before do post :create, params: { email: @resource.email, redirect_url: @redirect_url } @mail = ActionMailer::Base.deliveries.last @mail_redirect_url = CGI.unescape(@mail.body.match(/redirect_url=([^&]*)&/)[1]) @mail_reset_token = @mail.body.match(/reset_password_token=(.*)"/)[1] @resource.reload end describe 'reset_password_token is valid' do test 'mail_reset_token should be the same as reset_password_token' do assert_equal Devise.token_generator.digest(self, :reset_password_token, @mail_reset_token), @resource.reset_password_token end test 'reset_password_token should not be rewritten by origin mail_reset_token' do get :edit, params: { reset_password_token: @mail_reset_token, redirect_url: @mail_redirect_url } @resource.reload assert_equal Devise.token_generator.digest(self, :reset_password_token, @mail_reset_token), @resource.reset_password_token end test 'response should return success status' do get :edit, params: { reset_password_token: @mail_reset_token, redirect_url: @mail_redirect_url } assert_equal 302, response.status end test 'reset_password_sent_at should be valid' do assert_equal @resource.reset_password_period_valid?, true get :edit, params: { reset_password_token: @mail_reset_token, redirect_url: @mail_redirect_url } @resource.reload assert_equal Devise.token_generator.digest(self, :reset_password_token, @mail_reset_token), @resource.reset_password_token end test 'reset_password_sent_at should be expired' do assert_equal @resource.reset_password_period_valid?, true @resource.update reset_password_sent_at: @resource.reset_password_sent_at - Devise.reset_password_within - 1.seconds assert_equal @resource.reset_password_period_valid?, false assert_raises(ActionController::RoutingError) { get :edit, params: { reset_password_token: @mail_reset_token, redirect_url: @mail_redirect_url } } end end describe 'reset_password_token is not valid' do test 'response should return error status' do @resource.update reset_password_token: 'koskoskoskos' assert_not_equal Devise.token_generator.digest(self, :reset_password_token, @mail_reset_token), @resource.reset_password_token assert_raises(ActionController::RoutingError) { get :edit, params: { reset_password_token: @mail_reset_token, redirect_url: @mail_redirect_url } } end end end end describe 'Using default_password_reset_url' do before do @resource = create(:user, :confirmed) @redirect_url = 'http://ng-token-auth.dev' DeviseJwtAuth.default_password_reset_url = @redirect_url post :create, params: { email: @resource.email, redirect_url: @redirect_url } @mail = ActionMailer::Base.deliveries.last @resource.reload @sent_redirect_url = CGI.unescape(@mail.body.match(/redirect_url=([^&]*)&/)[1]) end teardown do DeviseJwtAuth.default_password_reset_url = nil end test 'response should return success status' do assert_equal 200, response.status end test 'action should send an email' do assert @mail end test 'the email body should contain a link with redirect url as a query param' do assert_equal @redirect_url, @sent_redirect_url end end describe 'Using redirect_whitelist' do before do @good_redirect_url = @redirect_url @bad_redirect_url = Faker::Internet.url DeviseJwtAuth.redirect_whitelist = [@good_redirect_url] end teardown do DeviseJwtAuth.redirect_whitelist = nil end describe 'for create' do test 'request to whitelisted redirect should be successful' do post :create, params: { email: @resource.email, redirect_url: @good_redirect_url } assert_equal 200, response.status end test 'request to non-whitelisted redirect should fail' do post :create, params: { email: @resource.email, redirect_url: @bad_redirect_url } assert_equal 422, response.status end test 'request to non-whitelisted redirect should return error message' do post :create, params: { email: @resource.email, redirect_url: @bad_redirect_url } @data = JSON.parse(response.body) assert @data['errors'] assert_equal @data['errors'], [I18n.t('devise_jwt_auth.passwords.not_allowed_redirect_url', redirect_url: @bad_redirect_url)] end end describe 'for edit' do before do @auth_headers = @resource.create_named_token_pair @new_password = Faker::Internet.password get_reset_token end test 'request to whitelisted redirect should be successful' do get :edit, params: { reset_password_token: @mail_reset_token, redirect_url: @good_redirect_url } assert_equal 302, response.status end test 'request to non-whitelisted redirect should fail' do get :edit, params: { reset_password_token: @mail_reset_token, redirect_url: @bad_redirect_url } assert_equal 422, response.status end test 'request to non-whitelisted redirect should return error message' do get :edit, params: { reset_password_token: @mail_reset_token, redirect_url: @bad_redirect_url } @data = JSON.parse(response.body) assert @data['errors'] assert_equal @data['errors'], [I18n.t('devise_jwt_auth.passwords.not_allowed_redirect_url', redirect_url: @bad_redirect_url)] end end end describe 'change password with current password required' do before do DeviseJwtAuth.check_current_password_before_update = :password end after do DeviseJwtAuth.check_current_password_before_update = false end describe 'success' do before do DeviseJwtAuth.require_client_password_reset_token = false @auth_headers = @resource.create_named_token_pair request.headers.merge!(@auth_headers) @new_password = Faker::Internet.password @resource.update password: 'secret123', password_confirmation: 'secret123' put :update, params: { password: @new_password, password_confirmation: @new_password, current_password: 'secret123' } @data = JSON.parse(response.body) @resource.reload end test 'request should be successful' do assert_equal 200, response.status end test 'response should have access token' do assert @data[DeviseJwtAuth.access_token_name] end test 'response should have refresh cookie' do assert response.cookies[DeviseJwtAuth.refresh_token_name] end end describe 'success with after password reset' do before do # create a new password reset request post :create, params: { email: @resource.email, redirect_url: @redirect_url } @mail = ActionMailer::Base.deliveries.last @mail_redirect_url = CGI.unescape(@mail.body.match(/redirect_url=([^&]*)&/)[1]) @mail_reset_token = @mail.body.match(/reset_password_token=(.*)"/)[1] # confirm via password reset email link get :edit, params: { reset_password_token: @mail_reset_token, redirect_url: @mail_redirect_url } @resource.reload @allow_password_change_after_reset = @resource.allow_password_change @auth_headers = @resource.create_named_token_pair request.headers.merge!(@auth_headers) @new_password = Faker::Internet.password put :update, params: { password: @new_password, password_confirmation: @new_password } @data = JSON.parse(response.body) @resource.reload @allow_password_change = @resource.allow_password_change @resource.reload end test 'request should be successful' do assert_equal 200, response.status end test 'response should have access token' do assert @data[DeviseJwtAuth.access_token_name] end test 'response should have refresh cookie' do assert response.cookies[DeviseJwtAuth.refresh_token_name] end test 'changes allow_password_change to true on reset' do assert_equal true, @allow_password_change_after_reset end test 'sets allow_password_change false' do assert_equal false, @allow_password_change end end describe 'current password mismatch error' do before do DeviseJwtAuth.require_client_password_reset_token = false @auth_headers = @resource.create_named_token_pair request.headers.merge!(@auth_headers) @new_password = Faker::Internet.password put :update, params: { password: @new_password, password_confirmation: @new_password, current_password: 'not_very_secret321' } @data = JSON.parse(response.body) end test 'response should fail unauthorized' do assert_equal 422, response.status end test 'response should not have access token' do assert_nil @data[DeviseJwtAuth.access_token_name] end test 'response should not have refresh cookie' do assert_nil response.cookies[DeviseJwtAuth.refresh_token_name] end end end describe 'change password' do describe 'using reset token' do before do DeviseJwtAuth.require_client_password_reset_token = true @redirect_url = 'http://client-app.dev' get_reset_token edit_url = CGI.unescape(@mail.body.match(/href="(.+)"/)[1]) query_parts = Rack::Utils.parse_nested_query(URI.parse(edit_url).query) get :edit, params: query_parts end test 'reponse should be redirect' do assert_equal 302, response.status end test 'reponse should redirect to correct redirect url' do host = URI.parse(response.location).host query_parts = Rack::Utils.parse_nested_query(URI.parse(response.location).query) assert_equal 'client-app.dev', host assert_equal @mail_reset_token, query_parts['reset_password_token'] assert_equal 1, query_parts.keys.size end test 'redirect should not have access token' do query_parts = Rack::Utils.parse_nested_query(URI.parse(response.location).query) assert_nil query_parts[DeviseJwtAuth.access_token_name] end test 'response should not have refresh cookie' do assert_nil response.cookies[DeviseJwtAuth.refresh_token_name] end teardown do DeviseJwtAuth.require_client_password_reset_token = false end end describe 'with valid headers' do before do @auth_headers = @resource.create_named_token_pair request.headers.merge!(@auth_headers) @new_password = Faker::Internet.password put :update, params: { password: @new_password, password_confirmation: @new_password } @data = JSON.parse(response.body) @resource.reload end test 'request should be successful' do assert_equal 200, response.status end test 'response should have access token' do assert @data[DeviseJwtAuth.access_token_name] end test 'request should return success message' do assert @data['message'] assert_equal @data['message'], I18n.t('devise_jwt_auth.passwords.successfully_updated') end test 'response should have refresh cookie' do assert response.cookies[DeviseJwtAuth.refresh_token_name] end test 'new password should authenticate user' do assert @resource.valid_password?(@new_password) end test 'reset_password_token should be removed' do assert_nil @resource.reset_password_token end end describe 'password mismatch error' do before do @auth_headers = @resource.create_named_token_pair request.headers.merge!(@auth_headers) @new_password = Faker::Internet.password put :update, params: { password: 'chong', password_confirmation: 'bong' } @data = JSON.parse(response.body) end test 'response should fail' do assert_equal 422, response.status end test 'response should not have access token' do assert_nil @data[DeviseJwtAuth.access_token_name] end test 'response should not have refresh cookie' do assert_nil response.cookies[DeviseJwtAuth.refresh_token_name] end end describe 'without valid headers' do before do @auth_headers = @resource.create_named_token_pair new_password = Faker::Internet.password put :update, params: { password: new_password, password_confirmation: new_password } @data = JSON.parse(response.body) end test 'response should fail' do assert_equal 401, response.status end test 'response should not have access token' do assert_nil @data[DeviseJwtAuth.access_token_name] end test 'response should not have refresh cookie' do assert_nil response.cookies[DeviseJwtAuth.refresh_token_name] end end describe 'with valid reset password token' do before do reset_password_token = @resource.send_reset_password_instructions @new_password = Faker::Internet.password @params = { password: @new_password, password_confirmation: @new_password, reset_password_token: reset_password_token } end describe 'with require_client_password_reset_token disabled' do before do DeviseJwtAuth.require_client_password_reset_token = false put :update, params: @params @data = JSON.parse(response.body) @resource.reload end test 'request should be not be successful' do assert_equal 401, response.status end test 'response should not have access token' do assert_nil @data[DeviseJwtAuth.access_token_name] end test 'response should not have refresh cookie' do assert_nil response.cookies[DeviseJwtAuth.refresh_token_name] end end describe 'with require_client_password_reset_token enabled' do before do DeviseJwtAuth.require_client_password_reset_token = true put :update, params: @params @data = JSON.parse(response.body) @resource.reload end test 'request should be successful' do assert_equal 200, response.status end test 'response should have access token' do assert @data[DeviseJwtAuth.access_token_name] end test 'request should return success message' do assert @data['message'] assert_equal @data['message'], I18n.t('devise_jwt_auth.passwords.successfully_updated') end test 'response should have refresh cookie' do assert response.cookies[DeviseJwtAuth.refresh_token_name] end test 'new password should authenticate user' do assert @resource.valid_password?(@new_password) end teardown do DeviseJwtAuth.require_client_password_reset_token = false end end end describe 'with invalid reset password token' do before do DeviseJwtAuth.require_client_password_reset_token = true @resource.update reset_password_token: 'koskoskoskos' put :update, params: @params @data = JSON.parse(response.body) @resource.reload end test 'request should fail' do assert_equal 401, response.status end test 'response should not have access token' do assert_nil @data[DeviseJwtAuth.access_token_name] end test 'response should not have refresh cookie' do assert_nil response.cookies[DeviseJwtAuth.refresh_token_name] end test 'new password should not authenticate user' do assert !@resource.valid_password?(@new_password) end teardown do DeviseJwtAuth.require_client_password_reset_token = false end end end end describe 'Alternate user class' do setup do @request.env['devise.mapping'] = Devise.mappings[:mang] end teardown do @request.env['devise.mapping'] = Devise.mappings[:user] end before do @resource = create(:mang_user, :confirmed) @redirect_url = 'http://ng-token-auth.dev' get_reset_token end test 'response should return success status' do assert_equal 200, response.status end test 'the email body should contain a link with reset token as a query param' do user = Mang.reset_password_by_token(reset_password_token: @mail_reset_token) assert_equal user.id, @resource.id end end describe 'unconfirmed user' do before do @resource = create(:user) @redirect_url = 'http://ng-token-auth.dev' get_reset_token get :edit, params: { reset_password_token: @mail_reset_token, redirect_url: @mail_redirect_url } @resource.reload end end describe 'unconfirmable user' do setup do @request.env['devise.mapping'] = Devise.mappings[:unconfirmable_user] end teardown do @request.env['devise.mapping'] = Devise.mappings[:user] end before do @resource = unconfirmable_users(:user) get_reset_token get :edit, params: { reset_password_token: @mail_reset_token, redirect_url: @mail_redirect_url } @resource.reload end end describe 'alternate user type' do before do @resource = create(:user, :confirmed) @redirect_url = 'http://ng-token-auth.dev' @config_name = 'altUser' params = { email: @resource.email, redirect_url: @redirect_url, config_name: @config_name } get_reset_token params end test 'config_name param is included in the confirmation email link' do assert_equal @config_name, @mail_config_name end end def get_reset_token(params = nil) params ||= { email: @resource.email, redirect_url: @redirect_url } post :create, params: params @mail = ActionMailer::Base.deliveries.last @resource.reload @mail_config_name = CGI.unescape(@mail.body.match(/config=([^&]*)&/)[1]) @mail_redirect_url = CGI.unescape(@mail.body.match(/redirect_url=([^&]*)&/)[1]) @mail_reset_token = @mail.body.match(/reset_password_token=(.*)"/)[1] end end end