From 6486fee744384d6cb0975b969491c788a028929d Mon Sep 17 00:00:00 2001
From: Ahmad Farhat <ahmad.af.farhat@gmail.com>
Date: Wed, 1 Nov 2023 11:31:08 -0400
Subject: [PATCH] Changes to local accounts (#5489)

* Changes to local accounts

* rspec

* esf
---
 app/assets/locales/en.json                           |  2 ++
 app/controllers/api/v1/api_controller.rb             |  6 ++++--
 app/controllers/api/v1/env_controller.rb             |  2 +-
 app/controllers/api/v1/sessions_controller.rb        |  6 ++++++
 app/controllers/api/v1/users_controller.rb           |  2 +-
 .../users/password_management/ResetPassword.jsx      |  3 +++
 .../hooks/mutations/sessions/useCreateSession.jsx    |  2 ++
 app/javascript/hooks/mutations/users/useResetPwd.jsx |  2 +-
 spec/controllers/users_controller_spec.rb            | 12 ++++++------
 9 files changed, 26 insertions(+), 11 deletions(-)

diff --git a/app/assets/locales/en.json b/app/assets/locales/en.json
index 2fec3e4d..8d58b358 100644
--- a/app/assets/locales/en.json
+++ b/app/assets/locales/en.json
@@ -81,6 +81,7 @@
       "account_info": "Account Info",
       "delete_account": "Delete Account",
       "change_password": "Change Password",
+      "set_password": "Set Your New Password",
       "reset_password": "Reset Password",
       "update_account_info": "Update Account Info",
       "current_password": "Current Password",
@@ -358,6 +359,7 @@
         "user_updated": "The user has been updated.",
         "user_deleted": "The user has been deleted.",
         "avatar_updated": "The avatar has been updated.",
+        "password_changed": "Successfully updated your password. Please sign in again.",
         "password_updated": "The password has been updated.",
         "account_activated": "Your account has been activated.",
         "activation_email_sent": "An email that contains the instructions to activate your account has been sent.",
diff --git a/app/controllers/api/v1/api_controller.rb b/app/controllers/api/v1/api_controller.rb
index 92b27351..0ebbe53f 100644
--- a/app/controllers/api/v1/api_controller.rb
+++ b/app/controllers/api/v1/api_controller.rb
@@ -90,8 +90,10 @@ module Api
       end
 
       # Checks if external authentication is enabled (currently only OIDC is implemented)
-      def external_authn_enabled?
-        ENV['OPENID_CONNECT_ISSUER'].present?
+      def external_auth?
+        return ENV['OPENID_CONNECT_ISSUER'].present? if ENV['LOADBALANCER_ENDPOINT'].blank?
+
+        !Tenant.exists?(name: current_provider, client_secret: 'local')
       end
     end
   end
diff --git a/app/controllers/api/v1/env_controller.rb b/app/controllers/api/v1/env_controller.rb
index 946e261c..4982b208 100644
--- a/app/controllers/api/v1/env_controller.rb
+++ b/app/controllers/api/v1/env_controller.rb
@@ -25,7 +25,7 @@ module Api
       # Returns basic NON-CONFIDENTIAL information on the environment variables
       def index
         render_data data: {
-          EXTERNAL_AUTH: ENV['OPENID_CONNECT_ISSUER'].present?, # currently only OIDC is implemented
+          EXTERNAL_AUTH: external_auth?,
           HCAPTCHA_KEY: ENV.fetch('HCAPTCHA_SITE_KEY', nil),
           VERSION_TAG: ENV.fetch('VERSION_TAG', ''),
           CURRENT_PROVIDER: current_provider,
diff --git a/app/controllers/api/v1/sessions_controller.rb b/app/controllers/api/v1/sessions_controller.rb
index 65a07e91..41eb19cb 100644
--- a/app/controllers/api/v1/sessions_controller.rb
+++ b/app/controllers/api/v1/sessions_controller.rb
@@ -45,6 +45,12 @@ module Api
         # Will return an error if the user is NOT from the current provider and if the user is NOT a super admin
         return render_error if user.provider != current_provider && !user.super_admin?
 
+        # Password is not set (local user migrated from v2)
+        if user.external_id.blank? && user.password_digest.blank?
+          token = user.generate_reset_token!
+          return render_error data: token, errors: 'PasswordNotSet'
+        end
+
         # TODO: Add proper error logging for non-verified token hcaptcha
         if user.authenticate(session_params[:password])
           return render_error data: user.id, errors: Rails.configuration.custom_error_msgs[:unverified_user] unless user.verified?
diff --git a/app/controllers/api/v1/users_controller.rb b/app/controllers/api/v1/users_controller.rb
index f00dd0e3..98b52024 100644
--- a/app/controllers/api/v1/users_controller.rb
+++ b/app/controllers/api/v1/users_controller.rb
@@ -39,7 +39,7 @@ module Api
       # POST /api/v1/users.json
       # Creates and saves a new user record in the database with the provided parameters
       def create
-        return render_error status: :forbidden if external_authn_enabled?
+        return render_error status: :forbidden if external_auth?
 
         # Check if this is an admin creating a user
         admin_create = current_user && PermissionsChecker.new(current_user:, permission_names: 'ManageUsers', current_provider:).call
diff --git a/app/javascript/components/users/password_management/ResetPassword.jsx b/app/javascript/components/users/password_management/ResetPassword.jsx
index b3ace1a9..56c35ab5 100644
--- a/app/javascript/components/users/password_management/ResetPassword.jsx
+++ b/app/javascript/components/users/password_management/ResetPassword.jsx
@@ -17,11 +17,13 @@
 import React, { useEffect } from 'react';
 import Card from 'react-bootstrap/Card';
 import { useParams } from 'react-router-dom';
+import { useTranslation } from 'react-i18next';
 import useVerifyToken from '../../../hooks/mutations/users/useVerifyToken';
 import ResetPwdForm from './forms/ResetPwdForm';
 import Logo from '../../shared_components/Logo';
 
 export default function ResetPassword() {
+  const { t } = useTranslation();
   const { token } = useParams();
   const verifyTokenAPI = useVerifyToken(token);
 
@@ -37,6 +39,7 @@ export default function ResetPassword() {
         <Logo />
       </div>
       <Card className="col-xl-5 col-lg-6 col-md-8 col-10 mx-auto p-4 border-0 card-shadow">
+        <Card.Title className="text-center pb-2"> { t('user.account.set_password') } </Card.Title>
         <ResetPwdForm token={token} />
       </Card>
     </div>
diff --git a/app/javascript/hooks/mutations/sessions/useCreateSession.jsx b/app/javascript/hooks/mutations/sessions/useCreateSession.jsx
index 80186549..10fef82e 100644
--- a/app/javascript/hooks/mutations/sessions/useCreateSession.jsx
+++ b/app/javascript/hooks/mutations/sessions/useCreateSession.jsx
@@ -49,6 +49,8 @@ export default function useCreateSession() {
           toast.error(t('toast.error.users.banned'));
         } else if (err.response.data.errors === 'UnverifiedUser') {
           navigate(`/verify?id=${err.response.data.data}`);
+        } else if (err.response.data.errors === 'PasswordNotSet') {
+          navigate(`/reset_password/${err.response.data.data}`);
         } else {
           toast.error(t('toast.error.session.invalid_credentials'));
         }
diff --git a/app/javascript/hooks/mutations/users/useResetPwd.jsx b/app/javascript/hooks/mutations/users/useResetPwd.jsx
index d2aae133..8ff67d46 100644
--- a/app/javascript/hooks/mutations/users/useResetPwd.jsx
+++ b/app/javascript/hooks/mutations/users/useResetPwd.jsx
@@ -28,7 +28,7 @@ export default function useResetPwd() {
     (user) => axios.post('/reset_password/reset.json', { user }),
     {
       onSuccess: () => {
-        toast.success(t('toast.success.user.password_updated'));
+        toast.success(t('toast.success.user.password_changed'));
         navigate('/signin');
       },
       onError: () => {
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index 7c61036e..e7f59cde 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -25,7 +25,7 @@ RSpec.describe Api::V1::UsersController, type: :controller do
 
   before do
     ENV['SMTP_SERVER'] = 'test.com'
-    allow(controller).to receive(:external_authn_enabled?).and_return(false)
+    allow(controller).to receive(:external_auth?).and_return(false)
     request.headers['ACCEPT'] = 'application/json'
   end
 
@@ -280,7 +280,7 @@ RSpec.describe Api::V1::UsersController, type: :controller do
 
     context 'External AuthN enabled' do
       before do
-        allow(controller).to receive(:external_authn_enabled?).and_return(true)
+        allow(controller).to receive(:external_auth?).and_return(true)
       end
 
       it 'returns :forbidden without creating the user account' do
@@ -472,9 +472,9 @@ RSpec.describe Api::V1::UsersController, type: :controller do
   end
 
   context 'private methods' do
-    describe '#external_authn_enabled?' do
+    describe '#external_auth??' do
       before do
-        allow(controller).to receive(:external_authn_enabled?).and_call_original
+        allow(controller).to receive(:external_auth?).and_call_original
       end
 
       context 'OPENID_CONNECT_ISSUER is present?' do
@@ -483,7 +483,7 @@ RSpec.describe Api::V1::UsersController, type: :controller do
         end
 
         it 'returns true' do
-          expect(controller).to be_external_authn_enabled
+          expect(controller).to be_external_auth
         end
       end
 
@@ -493,7 +493,7 @@ RSpec.describe Api::V1::UsersController, type: :controller do
         end
 
         it 'returns false' do
-          expect(controller).not_to be_external_authn_enabled
+          expect(controller).not_to be_external_auth
         end
       end
     end
-- 
GitLab