diff --git a/config/request_dev.ts b/config/request_dev.ts index d48e220..7357ff2 100644 --- a/config/request_dev.ts +++ b/config/request_dev.ts @@ -159,17 +159,21 @@ export const handleRequestConfig: RequestConfig = { ); } + // Rebuild request options from config + const originalConfig = response.config; const newOptions = { - ...options, + method: originalConfig.method, headers: { - ...(options.headers || {}), + ...(originalConfig.headers || {}), Authorization: `${newToken}`, }, + data: originalConfig.data, + params: originalConfig.params, skipAuthRefresh: true, }; // Gọi lại request gốc với accessToken mới - return request(response.url, newOptions); + return request(originalConfig.url, newOptions); } if ( diff --git a/config/request_prod.ts b/config/request_prod.ts index bc15a3b..74b2270 100644 --- a/config/request_prod.ts +++ b/config/request_prod.ts @@ -149,17 +149,21 @@ export const handleRequestConfig: RequestConfig = { ); } + // Rebuild request options from config + const originalConfig = response.config; const newOptions = { - ...options, + method: originalConfig.method, headers: { - ...(options.headers || {}), + ...(originalConfig.headers || {}), Authorization: `${newToken}`, }, + data: originalConfig.data, + params: originalConfig.params, skipAuthRefresh: true, }; // Gọi lại request gốc với accessToken mới - return request(response.url, newOptions); + return request(originalConfig.url, newOptions); } if ( diff --git a/package-lock.json b/package-lock.json index 7aea81f..1288b60 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "smatec-frontend", + "name": "SMATEC-FRONTEND", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/src/constants/api.ts b/src/constants/api.ts index f6fb18f..43864a6 100644 --- a/src/constants/api.ts +++ b/src/constants/api.ts @@ -1,9 +1,16 @@ // Auth API Paths -export const API_PATH_LOGIN = '/api/tokens'; +export const API_PATH_LOGIN = '/api/login'; +export const API_PATH_LOGIN_2FA = '/api/login/2fa'; +export const API_PATH_LOGOUT = '/api/logout'; export const API_PATH_REFRESH_TOKEN = '/api/keys/refresh'; export const API_PATH_GET_PROFILE = '/api/users/profile'; export const API_CHANGE_PASSWORD = '/api/password'; export const API_FORGOT_PASSWORD = '/api/password/reset-request'; +export const API_ENABLE_2FA = '/api/users/2fa/enable'; +export const API_ENABLE_2FA_VERIFY = '/api/users/2fa/verify'; +export const API_DISABLE_2FA = '/api/users/2fa/disable'; +export const API_ADMIN_DISABLE_2FA = '/api/2fa/disable/'; + // Alarm API Constants export const API_ALARMS = '/api/alarms'; export const API_ALARMS_CONFIRM = '/api/alarms/confirm'; diff --git a/src/locales/en-US/master/master-auth-en.ts b/src/locales/en-US/master/master-auth-en.ts index e65be53..568ac6c 100644 --- a/src/locales/en-US/master/master-auth-en.ts +++ b/src/locales/en-US/master/master-auth-en.ts @@ -5,10 +5,6 @@ export default { 'master.auth.validation.email': 'Email is required', 'master.auth.password': 'Password', 'master.auth.validation.password': 'Password is required', - 'master.auth.login.subtitle': 'Ship Monitoring System', - 'master.auth.login.description': 'Login to continue monitoring vessels', - 'master.auth.login.invalid': 'Invalid username or password', - 'master.auth.login.success': 'Login successful', 'master.auth.logout.title': 'Logout', 'master.auth.logout.confirm': 'Are you sure you want to logout?', 'master.auth.logout.success': 'Logout successful', @@ -18,6 +14,11 @@ export default { 'master.auth.forgot.message.success': 'Request sent successfully, please check your email!', 'master.auth.forgot.message.fail': 'Request failed, please try again later!', + 'master.auth.otp.error': 'Invalid OTP code. Please try again.', + 'master.auth.otp.button.title': 'Verify OTP', + 'master.auth.otp.placeholder': 'Enter OTP code', + 'master.auth.otp.required': 'OTP code is required', + 'master.auth.otp.length': 'OTP must be 6 digits', 'master.auth.reset.success': 'Password reset successful', 'master.auth.reset.error': 'An error occurred, please try again later!', 'master.auth.reset.invalid': diff --git a/src/locales/en-US/master/master-profile-en.ts b/src/locales/en-US/master/master-profile-en.ts index 77b4126..5def654 100644 --- a/src/locales/en-US/master/master-profile-en.ts +++ b/src/locales/en-US/master/master-profile-en.ts @@ -17,4 +17,28 @@ export default { 'master.profile.change-password.fail': 'Change password failed', 'master.profile.change-password.password.strong': 'A password contains at least 8 characters, including at least one number and includes both lower and uppercase letters and special characters, for example #, ?, !', + 'master.profile.2fa.status': '2FA Status', + 'master.profile.2fa.description': + 'Enable two-step verification to enhance the security of your account', + 'master.profile.2fa.enabled': 'Enabled', + 'master.profile.2fa.disabled': 'Disabled', + 'master.profile.2fa.setup.title': 'Set up two-step verification', + 'master.profile.2fa.verify': 'Confirm', + 'master.profile.2fa.cancel': 'Cancel', + 'master.profile.2fa.scan.instruction': + 'Scan the QR code with an authentication app (Google Authenticator, Authy, ...)', + 'master.profile.2fa.otp.instruction': + 'Enter the 6-digit code from the authentication app:', + 'master.profile.2fa.enable.error': 'Unable to enable 2FA. Please try again.', + 'master.profile.2fa.otp.invalid': 'Please enter a 6-digit OTP code', + 'master.profile.2fa.enable.success': '2FA enabled successfully!', + 'master.profile.2fa.verify.error': 'Invalid OTP code. Please try again.', + 'master.profile.2fa.disable.confirm.title': 'Confirm disable 2FA', + 'master.profile.2fa.disable.confirm.content': + 'Are you sure you want to disable two-step verification? This will reduce the security of your account.', + 'master.profile.2fa.disable.confirm.ok': 'Disable 2FA', + 'master.profile.2fa.disable.confirm.cancel': 'Cancel', + 'master.profile.2fa.disable.success': '2FA disabled successfully!', + 'master.profile.2fa.disable.error': + 'Unable to disable 2FA. Please try again.', }; diff --git a/src/locales/en-US/master/master-user-en.ts b/src/locales/en-US/master/master-user-en.ts index 5ab6a85..deda4ae 100644 --- a/src/locales/en-US/master/master-user-en.ts +++ b/src/locales/en-US/master/master-user-en.ts @@ -74,4 +74,12 @@ export default { 'master.users.resetPassword.modal.title': 'Reset Password For User', 'master.users.resetPassword.success': 'Password reset successful', 'master.users.resetPassword.error': 'Password reset failed', + 'master.users.disable2fa.title': 'Disable 2FA', + 'master.users.disable2fa.success': '2FA has been disabled successfully', + 'master.users.disable2fa.error': 'Failed to disable 2FA', + 'master.users.disable2fa.modal.title': 'Disable Two-Factor Authentication', + 'master.users.disable2fa.modal.warning': + 'Are you sure you want to disable 2FA for this user?', + 'master.users.disable2fa.modal.caution': + 'Warning: Disabling 2FA will reduce account security. The user will need to re-enable 2FA from their profile settings.', }; diff --git a/src/locales/vi-VN/master/master-auth-vi.ts b/src/locales/vi-VN/master/master-auth-vi.ts index 77899be..911ac45 100644 --- a/src/locales/vi-VN/master/master-auth-vi.ts +++ b/src/locales/vi-VN/master/master-auth-vi.ts @@ -3,9 +3,6 @@ export default { 'master.auth.login.email': 'Email', 'master.auth.login.title': 'Đăng nhập', 'master.auth.login.subtitle': 'Hệ thống giám sát tàu cá', - 'master.auth.login.description': 'Đăng nhập để tiếp tục giám sát tàu thuyền', - 'master.auth.login.invalid': 'Tên người dùng hoặc mật khẩu không hợp lệ', - 'master.auth.login.success': 'Đăng nhập thành công', 'master.auth.logout.title': 'Đăng xuất', 'master.auth.logout.confirm': 'Bạn có chắc chắn muốn đăng xuất?', 'master.auth.logout.success': 'Đăng xuất thành công', @@ -19,6 +16,11 @@ export default { 'Gửi yêu cầu thành công, vui lòng kiểm tra email của bạn!', 'master.auth.forgot.message.fail': 'Gửi yêu cầu thất bại, vui lòng thử lại sau!', + 'master.auth.otp.error': 'Mã OTP không hợp lệ. Vui lòng thử lại.', + 'master.auth.otp.button.title': 'Xác thực OTP', + 'master.auth.otp.placeholder': 'Nhập mã OTP', + 'master.auth.otp.required': 'Vui lòng nhập mã OTP', + 'master.auth.otp.length': 'Mã OTP phải có 6 chữ số', 'master.auth.reset.success': 'Đặt lại mật khẩu thành công', 'master.auth.reset.error': 'Có lỗi xảy ra, vui lòng thử lại sau!', 'master.auth.reset.invalid': diff --git a/src/locales/vi-VN/master/master-profile-vi.ts b/src/locales/vi-VN/master/master-profile-vi.ts index 10e4580..3efe2c9 100644 --- a/src/locales/vi-VN/master/master-profile-vi.ts +++ b/src/locales/vi-VN/master/master-profile-vi.ts @@ -17,4 +17,26 @@ export default { 'master.profile.change-profile.update-fail': 'Cập nhật thông tin thất bại', 'master.profile.change-password.success': 'Đổi mật khẩu thành công', 'master.profile.change-password.fail': 'Đổi mật khẩu thất bại', + 'master.profile.2fa.status': 'Trạng thái 2FA', + 'master.profile.2fa.description': + 'Bật xác thực 2 bước để tăng cường bảo mật cho tài khoản của bạn', + 'master.profile.2fa.enabled': 'Bật', + 'master.profile.2fa.disabled': 'Tắt', + 'master.profile.2fa.setup.title': 'Thiết lập xác thực 2 bước', + 'master.profile.2fa.verify': 'Xác nhận', + 'master.profile.2fa.cancel': 'Hủy', + 'master.profile.2fa.scan.instruction': + 'Quét mã QR bằng ứng dụng xác thực (Google Authenticator, Authy, ...)', + 'master.profile.2fa.otp.instruction': 'Nhập mã 6 số từ ứng dụng xác thực:', + 'master.profile.2fa.enable.error': 'Không thể bật 2FA. Vui lòng thử lại.', + 'master.profile.2fa.otp.invalid': 'Vui lòng nhập mã OTP 6 số', + 'master.profile.2fa.enable.success': 'Bật 2FA thành công!', + 'master.profile.2fa.verify.error': 'Mã OTP không đúng. Vui lòng thử lại.', + 'master.profile.2fa.disable.confirm.title': 'Xác nhận tắt 2FA', + 'master.profile.2fa.disable.confirm.content': + 'Bạn có chắc chắn muốn tắt xác thực 2 bước? Điều này sẽ giảm bảo mật cho tài khoản của bạn.', + 'master.profile.2fa.disable.confirm.ok': 'Tắt 2FA', + 'master.profile.2fa.disable.confirm.cancel': 'Hủy', + 'master.profile.2fa.disable.success': 'Đã tắt 2FA thành công!', + 'master.profile.2fa.disable.error': 'Không thể tắt 2FA. Vui lòng thử lại.', }; diff --git a/src/locales/vi-VN/master/master-user-vi.ts b/src/locales/vi-VN/master/master-user-vi.ts index b6f40ab..4bf08c2 100644 --- a/src/locales/vi-VN/master/master-user-vi.ts +++ b/src/locales/vi-VN/master/master-user-vi.ts @@ -73,4 +73,12 @@ export default { 'master.users.resetPassword.modal.title': 'Đặt lại mật khẩu cho người dùng', 'master.users.resetPassword.success': 'Đặt lại mật khẩu thành công', 'master.users.resetPassword.error': 'Đặt lại mật khẩu thất bại', + 'master.users.disable2fa.title': 'Tắt 2FA', + 'master.users.disable2fa.success': 'Đã tắt 2FA thành công', + 'master.users.disable2fa.error': 'Tắt 2FA thất bại', + 'master.users.disable2fa.modal.title': 'Tắt xác thực hai yếu tố', + 'master.users.disable2fa.modal.warning': + 'Bạn có chắc chắn muốn tắt 2FA cho người dùng này không?', + 'master.users.disable2fa.modal.caution': + 'Cảnh báo: Việc tắt 2FA sẽ làm giảm bảo mật tài khoản. Người dùng sẽ cần thiết lập lại 2FA từ cài đặt hồ sơ của họ.', }; diff --git a/src/pages/Auth/components/ForgotPasswordForm.tsx b/src/pages/Auth/components/ForgotPasswordForm.tsx new file mode 100644 index 0000000..a8c7438 --- /dev/null +++ b/src/pages/Auth/components/ForgotPasswordForm.tsx @@ -0,0 +1,47 @@ +import { UserOutlined } from '@ant-design/icons'; +import { ProFormText } from '@ant-design/pro-components'; +import { FormattedMessage, useIntl } from '@umijs/max'; + +const ForgotPasswordForm = () => { + const intl = useIntl(); + + return ( + <> + , + }} + placeholder={intl.formatMessage({ + id: 'master.auth.login.email', + defaultMessage: 'Email', + })} + rules={[ + { + required: true, + message: ( + + ), + }, + { + type: 'email', + message: ( + + ), + }, + ]} + /> + + ); +}; + +export default ForgotPasswordForm; diff --git a/src/pages/Auth/components/LoginForm.tsx b/src/pages/Auth/components/LoginForm.tsx new file mode 100644 index 0000000..f25cdb1 --- /dev/null +++ b/src/pages/Auth/components/LoginForm.tsx @@ -0,0 +1,68 @@ +import { LockOutlined, UserOutlined } from '@ant-design/icons'; +import { ProFormText } from '@ant-design/pro-components'; +import { FormattedMessage, useIntl } from '@umijs/max'; + +const LoginForm = () => { + const intl = useIntl(); + + return ( + <> + , + }} + placeholder={intl.formatMessage({ + id: 'master.auth.login.email', + defaultMessage: 'Email', + })} + rules={[ + { + required: true, + message: ( + + ), + }, + { + type: 'email', + message: ( + + ), + }, + ]} + /> + , + }} + placeholder={intl.formatMessage({ + id: 'master.auth.password', + defaultMessage: 'Mật khẩu', + })} + rules={[ + { + required: true, + message: intl.formatMessage({ + id: 'master.auth.validation.password', + defaultMessage: 'Mật khẩu không được để trống!', + }), + }, + ]} + /> + + ); +}; + +export default LoginForm; diff --git a/src/pages/Auth/components/OtpForm.tsx b/src/pages/Auth/components/OtpForm.tsx new file mode 100644 index 0000000..49c9baa --- /dev/null +++ b/src/pages/Auth/components/OtpForm.tsx @@ -0,0 +1,48 @@ +import { LockOutlined } from '@ant-design/icons'; +import { ProFormText } from '@ant-design/pro-components'; +import { FormattedMessage, useIntl } from '@umijs/max'; + +const OtpForm = () => { + const intl = useIntl(); + + return ( + <> + , + }} + placeholder={intl.formatMessage({ + id: 'master.auth.otp.placeholder', + defaultMessage: 'Enter OTP code', + })} + rules={[ + { + required: true, + message: ( + + ), + }, + { + len: 6, + message: ( + + ), + }, + ]} + /> + + ); +}; + +export default OtpForm; diff --git a/src/pages/Auth/index.tsx b/src/pages/Auth/index.tsx index fdec55c..3586cc6 100644 --- a/src/pages/Auth/index.tsx +++ b/src/pages/Auth/index.tsx @@ -6,6 +6,7 @@ import { ROUTER_HOME } from '@/constants/routes'; import { apiForgotPassword, apiLogin, + apiLogin2FA, apiQueryProfile, } from '@/services/master/AuthController'; import { checkRefreshTokenExpired } from '@/utils/jwt'; @@ -18,29 +19,32 @@ import { setAccessToken, setRefreshToken, } from '@/utils/storage'; -import { LockOutlined, UserOutlined } from '@ant-design/icons'; -import { LoginFormPage, ProFormText } from '@ant-design/pro-components'; +import { LoginFormPage } from '@ant-design/pro-components'; import { FormattedMessage, history, useIntl, useModel } from '@umijs/max'; import { Button, ConfigProvider, Flex, Image, message, theme } from 'antd'; import { CSSProperties, useEffect, useState } from 'react'; import { flushSync } from 'react-dom'; import mobifontLogo from '../../../public/mobifont-logo.png'; -type LoginType = 'login' | 'forgot'; +import ForgotPasswordForm from './components/ForgotPasswordForm'; +import LoginForm from './components/LoginForm'; +import OtpForm from './components/OtpForm'; + +type LoginType = 'login' | 'forgot' | 'otp'; // Form wrapper with animation const FormWrapper = ({ children, - key, + animationKey, }: { children: React.ReactNode; - key: string; + animationKey: string; }) => { const style: CSSProperties = { animation: 'fadeInSlide 0.4s ease-out forwards', }; return ( -
+