@digitaldefiance/express-suite-react-components 5.0.0 → 5.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +4 -0
- package/package.json +3 -4
- package/src/auth/AdminRoute.tsx +54 -0
- package/src/auth/Private.tsx +17 -0
- package/src/auth/PrivateRoute.tsx +28 -0
- package/src/auth/UnAuth.tsx +16 -0
- package/src/auth/UnAuthRoute.tsx +30 -0
- package/src/auth/{index.d.ts → index.ts} +2 -3
- package/src/components/ApiAccess.tsx +175 -0
- package/src/components/BackupCodeLoginForm.tsx +489 -0
- package/src/components/BackupCodesForm.tsx +287 -0
- package/src/components/CategoryMenu.tsx +155 -0
- package/src/components/ChangePasswordForm.tsx +273 -0
- package/src/components/ConfirmationDialog.tsx +48 -0
- package/src/components/CurrencyCodeSelector.tsx +60 -0
- package/src/components/CurrencyInput.tsx +80 -0
- package/src/components/DashboardPage.tsx +24 -0
- package/src/components/DropdownMenu.tsx +105 -0
- package/src/components/ExpirationSecondsSelector.tsx +60 -0
- package/src/components/FakeEmailAdminPanel.tsx +374 -0
- package/src/components/Flag.tsx +52 -0
- package/src/components/ForgotPasswordForm.tsx +174 -0
- package/src/components/LoginForm.tsx +468 -0
- package/src/components/LogoutPage.tsx +21 -0
- package/src/components/RegisterForm.tsx +1169 -0
- package/src/components/ResetPasswordForm.tsx +247 -0
- package/src/components/SideMenu.tsx +46 -0
- package/src/components/SideMenuListItem.tsx +75 -0
- package/src/components/T.tsx +24 -0
- package/src/components/TDiv.tsx +26 -0
- package/src/components/TImgAlt.tsx +28 -0
- package/src/components/TopMenu.tsx +252 -0
- package/src/components/TotpSetupForm.tsx +188 -0
- package/src/components/TotpVerificationForm.tsx +148 -0
- package/src/components/TranslatedTitle.tsx +31 -0
- package/src/components/UserLanguageSelector.tsx +45 -0
- package/src/components/UserMenu.tsx +15 -0
- package/src/components/UserSettingsForm.tsx +904 -0
- package/src/components/VerifyEmailPage.tsx +222 -0
- package/src/components/__tests__/RegisterForm.totp.spec.tsx +377 -0
- package/src/components/__tests__/TotpSetupForm.spec.tsx +258 -0
- package/src/components/__tests__/TotpVerificationForm.spec.tsx +259 -0
- package/src/components/__tests__/UserSettingsForm.totp.spec.tsx +396 -0
- package/src/components/{index.d.ts → index.ts} +1 -1
- package/src/contexts/AuthProvider.spec.tsx +1284 -0
- package/src/contexts/AuthProvider.tsx +977 -0
- package/src/contexts/I18nProvider.tsx +164 -0
- package/src/contexts/MenuContext.tsx +426 -0
- package/src/contexts/SuiteConfigProvider.tsx +93 -0
- package/src/contexts/ThemeProvider.tsx +67 -0
- package/src/contexts/{index.d.ts → index.ts} +0 -1
- package/src/hooks/{index.d.ts → index.ts} +0 -1
- package/src/hooks/useAuthenticatedApi.tsx +67 -0
- package/src/hooks/useBackupCodes.ts +105 -0
- package/src/hooks/useEmailVerification.ts +54 -0
- package/src/hooks/useExpiringValue.ts +78 -0
- package/src/hooks/useLocalStorage.ts +18 -0
- package/src/hooks/useUserSettings.ts +271 -0
- package/src/{index.d.ts → index.ts} +1 -1
- package/src/interfaces/IAppConfig.ts +7 -0
- package/src/interfaces/ICategoryConfig.ts +41 -0
- package/src/interfaces/IMenuConfig.ts +28 -0
- package/src/interfaces/IMenuOption.ts +79 -0
- package/src/interfaces/{index.d.ts → index.ts} +0 -1
- package/src/services/__mocks__/authService.ts +14 -0
- package/src/services/__tests__/authService.totp.property.spec.ts +360 -0
- package/src/services/__tests__/authService.totp.spec.ts +439 -0
- package/src/services/api.ts +13 -0
- package/src/services/authService.ts +692 -0
- package/src/services/authenticatedApi.ts +17 -0
- package/src/services/index.ts +3 -0
- package/src/types/MenuType.ts +15 -0
- package/src/types/expirationSeconds.ts +18 -0
- package/src/types/index.ts +1 -0
- package/src/types/translation.ts +20 -0
- package/src/wrappers/BackupCodeLoginWrapper.tsx +34 -0
- package/src/wrappers/BackupCodesWrapper.tsx +28 -0
- package/src/wrappers/ChangePasswordFormWrapper.tsx +34 -0
- package/src/wrappers/ForgotPasswordFormWrapper.tsx +45 -0
- package/src/wrappers/LoginFormWrapper.tsx +108 -0
- package/src/wrappers/LogoutPageWrapper.tsx +30 -0
- package/src/wrappers/RegisterFormWrapper.tsx +109 -0
- package/src/wrappers/ResetPasswordFormWrapper.tsx +57 -0
- package/src/wrappers/UserSettingsFormWrapper.tsx +140 -0
- package/src/wrappers/VerifyEmailPageWrapper.tsx +27 -0
- package/src/wrappers/__tests__/LoginFormWrapper.totp.spec.tsx +339 -0
- package/src/wrappers/{index.d.ts → index.tsx} +10 -1
- package/src/auth/AdminRoute.d.ts +0 -8
- package/src/auth/AdminRoute.d.ts.map +0 -1
- package/src/auth/AdminRoute.js +0 -28
- package/src/auth/Private.d.ts +0 -6
- package/src/auth/Private.d.ts.map +0 -1
- package/src/auth/Private.js +0 -14
- package/src/auth/PrivateRoute.d.ts +0 -8
- package/src/auth/PrivateRoute.d.ts.map +0 -1
- package/src/auth/PrivateRoute.js +0 -23
- package/src/auth/UnAuth.d.ts +0 -6
- package/src/auth/UnAuth.d.ts.map +0 -1
- package/src/auth/UnAuth.js +0 -14
- package/src/auth/UnAuthRoute.d.ts +0 -8
- package/src/auth/UnAuthRoute.d.ts.map +0 -1
- package/src/auth/UnAuthRoute.js +0 -22
- package/src/auth/index.d.ts.map +0 -1
- package/src/auth/index.js +0 -12
- package/src/components/ApiAccess.d.ts +0 -16
- package/src/components/ApiAccess.d.ts.map +0 -1
- package/src/components/ApiAccess.js +0 -77
- package/src/components/BackupCodeLoginForm.d.ts +0 -43
- package/src/components/BackupCodeLoginForm.d.ts.map +0 -1
- package/src/components/BackupCodeLoginForm.js +0 -139
- package/src/components/BackupCodesForm.d.ts +0 -26
- package/src/components/BackupCodesForm.d.ts.map +0 -1
- package/src/components/BackupCodesForm.js +0 -120
- package/src/components/CategoryMenu.d.ts +0 -17
- package/src/components/CategoryMenu.d.ts.map +0 -1
- package/src/components/CategoryMenu.js +0 -65
- package/src/components/ChangePasswordForm.d.ts +0 -26
- package/src/components/ChangePasswordForm.d.ts.map +0 -1
- package/src/components/ChangePasswordForm.js +0 -78
- package/src/components/ConfirmationDialog.d.ts +0 -13
- package/src/components/ConfirmationDialog.d.ts.map +0 -1
- package/src/components/ConfirmationDialog.js +0 -10
- package/src/components/CurrencyCodeSelector.d.ts +0 -9
- package/src/components/CurrencyCodeSelector.d.ts.map +0 -1
- package/src/components/CurrencyCodeSelector.js +0 -31
- package/src/components/CurrencyInput.d.ts +0 -13
- package/src/components/CurrencyInput.d.ts.map +0 -1
- package/src/components/CurrencyInput.js +0 -22
- package/src/components/DashboardPage.d.ts +0 -8
- package/src/components/DashboardPage.d.ts.map +0 -1
- package/src/components/DashboardPage.js +0 -10
- package/src/components/DropdownMenu.d.ts +0 -13
- package/src/components/DropdownMenu.d.ts.map +0 -1
- package/src/components/DropdownMenu.js +0 -63
- package/src/components/ExpirationSecondsSelector.d.ts +0 -13
- package/src/components/ExpirationSecondsSelector.d.ts.map +0 -1
- package/src/components/ExpirationSecondsSelector.js +0 -32
- package/src/components/FakeEmailAdminPanel.d.ts +0 -23
- package/src/components/FakeEmailAdminPanel.d.ts.map +0 -1
- package/src/components/FakeEmailAdminPanel.js +0 -147
- package/src/components/Flag.d.ts +0 -20
- package/src/components/Flag.d.ts.map +0 -1
- package/src/components/Flag.js +0 -43
- package/src/components/ForgotPasswordForm.d.ts +0 -18
- package/src/components/ForgotPasswordForm.d.ts.map +0 -1
- package/src/components/ForgotPasswordForm.js +0 -61
- package/src/components/LoginForm.d.ts +0 -44
- package/src/components/LoginForm.d.ts.map +0 -1
- package/src/components/LoginForm.js +0 -123
- package/src/components/LogoutPage.d.ts +0 -8
- package/src/components/LogoutPage.d.ts.map +0 -1
- package/src/components/LogoutPage.js +0 -16
- package/src/components/RegisterForm.d.ts +0 -100
- package/src/components/RegisterForm.d.ts.map +0 -1
- package/src/components/RegisterForm.js +0 -306
- package/src/components/ResetPasswordForm.d.ts +0 -23
- package/src/components/ResetPasswordForm.d.ts.map +0 -1
- package/src/components/ResetPasswordForm.js +0 -78
- package/src/components/SideMenu.d.ts +0 -8
- package/src/components/SideMenu.d.ts.map +0 -1
- package/src/components/SideMenu.js +0 -25
- package/src/components/SideMenuListItem.d.ts +0 -13
- package/src/components/SideMenuListItem.d.ts.map +0 -1
- package/src/components/SideMenuListItem.js +0 -44
- package/src/components/T.d.ts +0 -7
- package/src/components/T.d.ts.map +0 -1
- package/src/components/T.js +0 -13
- package/src/components/TDiv.d.ts +0 -9
- package/src/components/TDiv.d.ts.map +0 -1
- package/src/components/TDiv.js +0 -14
- package/src/components/TImgAlt.d.ts +0 -10
- package/src/components/TImgAlt.d.ts.map +0 -1
- package/src/components/TImgAlt.js +0 -14
- package/src/components/TopMenu.d.ts +0 -50
- package/src/components/TopMenu.d.ts.map +0 -1
- package/src/components/TopMenu.js +0 -79
- package/src/components/TotpSetupForm.d.ts +0 -24
- package/src/components/TotpSetupForm.d.ts.map +0 -1
- package/src/components/TotpSetupForm.js +0 -82
- package/src/components/TotpVerificationForm.d.ts +0 -23
- package/src/components/TotpVerificationForm.d.ts.map +0 -1
- package/src/components/TotpVerificationForm.js +0 -66
- package/src/components/TranslatedTitle.d.ts +0 -8
- package/src/components/TranslatedTitle.d.ts.map +0 -1
- package/src/components/TranslatedTitle.js +0 -15
- package/src/components/UserLanguageSelector.d.ts +0 -4
- package/src/components/UserLanguageSelector.d.ts.map +0 -1
- package/src/components/UserLanguageSelector.js +0 -31
- package/src/components/UserMenu.d.ts +0 -4
- package/src/components/UserMenu.d.ts.map +0 -1
- package/src/components/UserMenu.js +0 -12
- package/src/components/UserSettingsForm.d.ts +0 -113
- package/src/components/UserSettingsForm.d.ts.map +0 -1
- package/src/components/UserSettingsForm.js +0 -269
- package/src/components/VerifyEmailPage.d.ts +0 -24
- package/src/components/VerifyEmailPage.d.ts.map +0 -1
- package/src/components/VerifyEmailPage.js +0 -81
- package/src/components/index.d.ts.map +0 -1
- package/src/components/index.js +0 -35
- package/src/contexts/AuthProvider.d.ts +0 -186
- package/src/contexts/AuthProvider.d.ts.map +0 -1
- package/src/contexts/AuthProvider.js +0 -515
- package/src/contexts/I18nProvider.d.ts +0 -27
- package/src/contexts/I18nProvider.d.ts.map +0 -1
- package/src/contexts/I18nProvider.js +0 -60
- package/src/contexts/MenuContext.d.ts +0 -20
- package/src/contexts/MenuContext.d.ts.map +0 -1
- package/src/contexts/MenuContext.js +0 -275
- package/src/contexts/SuiteConfigProvider.d.ts +0 -44
- package/src/contexts/SuiteConfigProvider.d.ts.map +0 -1
- package/src/contexts/SuiteConfigProvider.js +0 -43
- package/src/contexts/ThemeProvider.d.ts +0 -15
- package/src/contexts/ThemeProvider.d.ts.map +0 -1
- package/src/contexts/ThemeProvider.js +0 -36
- package/src/contexts/index.d.ts.map +0 -1
- package/src/contexts/index.js +0 -8
- package/src/hooks/index.d.ts.map +0 -1
- package/src/hooks/index.js +0 -9
- package/src/hooks/useAuthenticatedApi.d.ts +0 -34
- package/src/hooks/useAuthenticatedApi.d.ts.map +0 -1
- package/src/hooks/useAuthenticatedApi.js +0 -46
- package/src/hooks/useBackupCodes.d.ts +0 -15
- package/src/hooks/useBackupCodes.d.ts.map +0 -1
- package/src/hooks/useBackupCodes.js +0 -74
- package/src/hooks/useEmailVerification.d.ts +0 -10
- package/src/hooks/useEmailVerification.d.ts.map +0 -1
- package/src/hooks/useEmailVerification.js +0 -40
- package/src/hooks/useExpiringValue.d.ts +0 -14
- package/src/hooks/useExpiringValue.d.ts.map +0 -1
- package/src/hooks/useExpiringValue.js +0 -53
- package/src/hooks/useLocalStorage.d.ts +0 -2
- package/src/hooks/useLocalStorage.d.ts.map +0 -1
- package/src/hooks/useLocalStorage.js +0 -15
- package/src/hooks/useUserSettings.d.ts +0 -48
- package/src/hooks/useUserSettings.d.ts.map +0 -1
- package/src/hooks/useUserSettings.js +0 -171
- package/src/index.d.ts.map +0 -1
- package/src/index.js +0 -12
- package/src/interfaces/IAppConfig.d.ts +0 -8
- package/src/interfaces/IAppConfig.d.ts.map +0 -1
- package/src/interfaces/IAppConfig.js +0 -2
- package/src/interfaces/ICategoryConfig.d.ts +0 -41
- package/src/interfaces/ICategoryConfig.d.ts.map +0 -1
- package/src/interfaces/ICategoryConfig.js +0 -2
- package/src/interfaces/IMenuConfig.d.ts +0 -28
- package/src/interfaces/IMenuConfig.d.ts.map +0 -1
- package/src/interfaces/IMenuConfig.js +0 -2
- package/src/interfaces/IMenuOption.d.ts +0 -82
- package/src/interfaces/IMenuOption.d.ts.map +0 -1
- package/src/interfaces/IMenuOption.js +0 -2
- package/src/interfaces/index.d.ts.map +0 -1
- package/src/interfaces/index.js +0 -7
- package/src/services/__mocks__/authService.d.ts +0 -21
- package/src/services/__mocks__/authService.d.ts.map +0 -1
- package/src/services/__mocks__/authService.js +0 -15
- package/src/services/api.d.ts +0 -3
- package/src/services/api.d.ts.map +0 -1
- package/src/services/api.js +0 -14
- package/src/services/authService.d.ts +0 -126
- package/src/services/authService.d.ts.map +0 -1
- package/src/services/authService.js +0 -471
- package/src/services/authenticatedApi.d.ts +0 -3
- package/src/services/authenticatedApi.d.ts.map +0 -1
- package/src/services/authenticatedApi.js +0 -18
- package/src/services/index.d.ts +0 -4
- package/src/services/index.d.ts.map +0 -1
- package/src/services/index.js +0 -6
- package/src/types/MenuType.d.ts +0 -11
- package/src/types/MenuType.d.ts.map +0 -1
- package/src/types/MenuType.js +0 -12
- package/src/types/expirationSeconds.d.ts +0 -3
- package/src/types/expirationSeconds.d.ts.map +0 -1
- package/src/types/expirationSeconds.js +0 -17
- package/src/types/index.d.ts +0 -2
- package/src/types/index.d.ts.map +0 -1
- package/src/types/index.js +0 -4
- package/src/types/translation.d.ts +0 -10
- package/src/types/translation.d.ts.map +0 -1
- package/src/types/translation.js +0 -9
- package/src/wrappers/BackupCodeLoginWrapper.d.ts +0 -8
- package/src/wrappers/BackupCodeLoginWrapper.d.ts.map +0 -1
- package/src/wrappers/BackupCodeLoginWrapper.js +0 -20
- package/src/wrappers/BackupCodesWrapper.d.ts +0 -7
- package/src/wrappers/BackupCodesWrapper.d.ts.map +0 -1
- package/src/wrappers/BackupCodesWrapper.js +0 -17
- package/src/wrappers/ChangePasswordFormWrapper.d.ts +0 -8
- package/src/wrappers/ChangePasswordFormWrapper.d.ts.map +0 -1
- package/src/wrappers/ChangePasswordFormWrapper.js +0 -21
- package/src/wrappers/ForgotPasswordFormWrapper.d.ts +0 -8
- package/src/wrappers/ForgotPasswordFormWrapper.d.ts.map +0 -1
- package/src/wrappers/ForgotPasswordFormWrapper.js +0 -29
- package/src/wrappers/LoginFormWrapper.d.ts +0 -21
- package/src/wrappers/LoginFormWrapper.d.ts.map +0 -1
- package/src/wrappers/LoginFormWrapper.js +0 -72
- package/src/wrappers/LogoutPageWrapper.d.ts +0 -9
- package/src/wrappers/LogoutPageWrapper.d.ts.map +0 -1
- package/src/wrappers/LogoutPageWrapper.js +0 -21
- package/src/wrappers/RegisterFormWrapper.d.ts +0 -20
- package/src/wrappers/RegisterFormWrapper.d.ts.map +0 -1
- package/src/wrappers/RegisterFormWrapper.js +0 -51
- package/src/wrappers/ResetPasswordFormWrapper.d.ts +0 -9
- package/src/wrappers/ResetPasswordFormWrapper.d.ts.map +0 -1
- package/src/wrappers/ResetPasswordFormWrapper.js +0 -34
- package/src/wrappers/UserSettingsFormWrapper.d.ts +0 -29
- package/src/wrappers/UserSettingsFormWrapper.d.ts.map +0 -1
- package/src/wrappers/UserSettingsFormWrapper.js +0 -95
- package/src/wrappers/VerifyEmailPageWrapper.d.ts +0 -8
- package/src/wrappers/VerifyEmailPageWrapper.d.ts.map +0 -1
- package/src/wrappers/VerifyEmailPageWrapper.js +0 -21
- package/src/wrappers/index.d.ts.map +0 -1
- package/src/wrappers/index.js +0 -24
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit Tests for AuthService TOTP Methods
|
|
3
|
+
*
|
|
4
|
+
* Tests each TOTP method calls the correct endpoint, returns the correct
|
|
5
|
+
* shape on success, and extracts errors on failure.
|
|
6
|
+
*
|
|
7
|
+
* Validates: Requirements 11.1, 11.2, 11.3, 11.4, 11.5
|
|
8
|
+
*
|
|
9
|
+
* @module services/__tests__/authService.totp.spec
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
|
|
13
|
+
import axios, { AxiosError, AxiosHeaders } from 'axios';
|
|
14
|
+
|
|
15
|
+
// ─── Mocks ──────────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
const mockPost = jest.fn() as jest.MockedFunction<typeof axios.prototype.post>;
|
|
18
|
+
const mockGet = jest.fn() as jest.MockedFunction<typeof axios.prototype.get>;
|
|
19
|
+
|
|
20
|
+
const mockAxiosInstance = {
|
|
21
|
+
post: mockPost,
|
|
22
|
+
get: mockGet,
|
|
23
|
+
interceptors: {
|
|
24
|
+
request: { use: jest.fn() },
|
|
25
|
+
response: { use: jest.fn() },
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
jest.mock('../api', () => ({
|
|
30
|
+
createApiClient: jest.fn(() => mockAxiosInstance),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
jest.mock('../authenticatedApi', () => ({
|
|
34
|
+
createAuthenticatedApiClient: jest.fn(() => mockAxiosInstance),
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
jest.mock('@digitaldefiance/ecies-lib', () => ({
|
|
38
|
+
EciesCryptoCore: jest.fn().mockImplementation(() => ({})),
|
|
39
|
+
ECIESService: jest.fn().mockImplementation(() => ({})),
|
|
40
|
+
hexToUint8Array: jest.fn(),
|
|
41
|
+
uint8ArrayToHex: jest.fn(),
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
jest.mock('@digitaldefiance/suite-core-lib', () => ({
|
|
45
|
+
getSuiteCoreTranslation: jest.fn((key: string) => key),
|
|
46
|
+
SuiteCoreStringKey: {
|
|
47
|
+
Common_UnexpectedError: 'common_unexpectedError',
|
|
48
|
+
Registration_Error: 'registration_error',
|
|
49
|
+
Registration_Success: 'registration_success',
|
|
50
|
+
Validation_UsernameOrEmailRequired: 'validation_usernameOrEmailRequired',
|
|
51
|
+
Validation_InvalidToken: 'validation_invalidToken',
|
|
52
|
+
Error_Login_DirectChallengeNotEnabled:
|
|
53
|
+
'error_login_directChallengeNotEnabled',
|
|
54
|
+
Error_Login_PasswordLoginNotEnabled: 'error_login_passwordLoginNotEnabled',
|
|
55
|
+
},
|
|
56
|
+
Constants: {},
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
// Import AuthService after mocks are set up
|
|
60
|
+
import { AuthService } from '../authService';
|
|
61
|
+
|
|
62
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Build a realistic AxiosError with the given status code and response data.
|
|
66
|
+
*/
|
|
67
|
+
function buildAxiosError(
|
|
68
|
+
status: number,
|
|
69
|
+
data: Record<string, unknown>,
|
|
70
|
+
): AxiosError {
|
|
71
|
+
const headers = new AxiosHeaders();
|
|
72
|
+
const config = { headers } as import('axios').InternalAxiosRequestConfig;
|
|
73
|
+
const response = {
|
|
74
|
+
data,
|
|
75
|
+
status,
|
|
76
|
+
statusText: 'Error',
|
|
77
|
+
headers: {},
|
|
78
|
+
config,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return new AxiosError(
|
|
82
|
+
`Request failed with status code ${status}`,
|
|
83
|
+
AxiosError.ERR_BAD_RESPONSE,
|
|
84
|
+
config,
|
|
85
|
+
undefined,
|
|
86
|
+
response,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ─── Tests ──────────────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
describe('AuthService TOTP - Unit Tests', () => {
|
|
93
|
+
let authService: AuthService;
|
|
94
|
+
|
|
95
|
+
beforeEach(() => {
|
|
96
|
+
mockPost.mockReset();
|
|
97
|
+
mockGet.mockReset();
|
|
98
|
+
|
|
99
|
+
const mockConstants =
|
|
100
|
+
{} as import('@digitaldefiance/suite-core-lib').IConstants;
|
|
101
|
+
const mockEciesConfig =
|
|
102
|
+
{} as import('@digitaldefiance/ecies-lib').IECIESConfig;
|
|
103
|
+
|
|
104
|
+
authService = new AuthService(
|
|
105
|
+
mockConstants,
|
|
106
|
+
'http://localhost:3000',
|
|
107
|
+
mockEciesConfig,
|
|
108
|
+
'test.example.com',
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// ─── setupTotp ──────────────────────────────────────────────────────────
|
|
113
|
+
// Validates: Requirement 11.1
|
|
114
|
+
|
|
115
|
+
describe('setupTotp', () => {
|
|
116
|
+
it('calls POST /user/totp/setup and returns { provisioningUri, secret } on success', async () => {
|
|
117
|
+
mockPost.mockResolvedValueOnce({
|
|
118
|
+
data: {
|
|
119
|
+
provisioningUri: 'otpauth://totp/Issuer:user@example.com?secret=ABCDEF',
|
|
120
|
+
secret: 'ABCDEF',
|
|
121
|
+
},
|
|
122
|
+
status: 200,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const result = await authService.setupTotp();
|
|
126
|
+
|
|
127
|
+
expect(mockPost).toHaveBeenCalledWith('/user/totp/setup');
|
|
128
|
+
expect(result).toEqual({
|
|
129
|
+
provisioningUri: 'otpauth://totp/Issuer:user@example.com?secret=ABCDEF',
|
|
130
|
+
secret: 'ABCDEF',
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('returns { error } when the backend returns a non-2xx response with message', async () => {
|
|
135
|
+
mockPost.mockRejectedValueOnce(
|
|
136
|
+
buildAxiosError(409, { message: 'TOTP is already active' }),
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const result = await authService.setupTotp();
|
|
140
|
+
|
|
141
|
+
expect(result).toEqual({ error: 'TOTP is already active' });
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('returns { error } when the backend returns a non-2xx response with error string', async () => {
|
|
145
|
+
mockPost.mockRejectedValueOnce(
|
|
146
|
+
buildAxiosError(500, { error: 'Internal server error' }),
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const result = await authService.setupTotp();
|
|
150
|
+
|
|
151
|
+
expect(result).toEqual({ error: 'Internal server error' });
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('returns fallback error when a non-Axios error is thrown', async () => {
|
|
155
|
+
mockPost.mockRejectedValueOnce(new Error('Network failure'));
|
|
156
|
+
|
|
157
|
+
const result = await authService.setupTotp();
|
|
158
|
+
|
|
159
|
+
expect(result).toHaveProperty('error');
|
|
160
|
+
expect(typeof (result as { error: string }).error).toBe('string');
|
|
161
|
+
expect((result as { error: string }).error.length).toBeGreaterThan(0);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// ─── confirmTotp ────────────────────────────────────────────────────────
|
|
166
|
+
// Validates: Requirement 11.2
|
|
167
|
+
|
|
168
|
+
describe('confirmTotp', () => {
|
|
169
|
+
it('calls POST /user/totp/confirm with { code } and returns { success: true } on success', async () => {
|
|
170
|
+
mockPost.mockResolvedValueOnce({
|
|
171
|
+
data: { message: 'TOTP confirmed' },
|
|
172
|
+
status: 200,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const result = await authService.confirmTotp('123456');
|
|
176
|
+
|
|
177
|
+
expect(mockPost).toHaveBeenCalledWith('/user/totp/confirm', {
|
|
178
|
+
code: '123456',
|
|
179
|
+
});
|
|
180
|
+
expect(result).toEqual({ success: true });
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('returns { error } when the backend returns a non-2xx response', async () => {
|
|
184
|
+
mockPost.mockRejectedValueOnce(
|
|
185
|
+
buildAxiosError(400, { message: 'Invalid TOTP code' }),
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
const result = await authService.confirmTotp('000000');
|
|
189
|
+
|
|
190
|
+
expect(result).toEqual({ error: 'Invalid TOTP code' });
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('returns { error } when no pending secret exists', async () => {
|
|
194
|
+
mockPost.mockRejectedValueOnce(
|
|
195
|
+
buildAxiosError(400, {
|
|
196
|
+
message: 'TOTP setup has not been initiated',
|
|
197
|
+
}),
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
const result = await authService.confirmTotp('123456');
|
|
201
|
+
|
|
202
|
+
expect(result).toEqual({
|
|
203
|
+
error: 'TOTP setup has not been initiated',
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('returns fallback error when a non-Axios error is thrown', async () => {
|
|
208
|
+
mockPost.mockRejectedValueOnce(new Error('Network failure'));
|
|
209
|
+
|
|
210
|
+
const result = await authService.confirmTotp('123456');
|
|
211
|
+
|
|
212
|
+
expect(result).toHaveProperty('error');
|
|
213
|
+
expect(typeof (result as { error: string }).error).toBe('string');
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// ─── disableTotp ────────────────────────────────────────────────────────
|
|
218
|
+
// Validates: Requirement 11.3
|
|
219
|
+
|
|
220
|
+
describe('disableTotp', () => {
|
|
221
|
+
it('calls POST /user/totp/disable with { code } and returns { success: true } on success', async () => {
|
|
222
|
+
mockPost.mockResolvedValueOnce({
|
|
223
|
+
data: { message: 'TOTP disabled' },
|
|
224
|
+
status: 200,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const result = await authService.disableTotp('654321');
|
|
228
|
+
|
|
229
|
+
expect(mockPost).toHaveBeenCalledWith('/user/totp/disable', {
|
|
230
|
+
code: '654321',
|
|
231
|
+
});
|
|
232
|
+
expect(result).toEqual({ success: true });
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('returns { error } when the backend returns a non-2xx response', async () => {
|
|
236
|
+
mockPost.mockRejectedValueOnce(
|
|
237
|
+
buildAxiosError(400, { message: 'Invalid TOTP code' }),
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
const result = await authService.disableTotp('000000');
|
|
241
|
+
|
|
242
|
+
expect(result).toEqual({ error: 'Invalid TOTP code' });
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('returns { error } when TOTP is not currently active', async () => {
|
|
246
|
+
mockPost.mockRejectedValueOnce(
|
|
247
|
+
buildAxiosError(409, {
|
|
248
|
+
message: 'TOTP is not currently active',
|
|
249
|
+
}),
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
const result = await authService.disableTotp('123456');
|
|
253
|
+
|
|
254
|
+
expect(result).toEqual({
|
|
255
|
+
error: 'TOTP is not currently active',
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('returns fallback error when a non-Axios error is thrown', async () => {
|
|
260
|
+
mockPost.mockRejectedValueOnce(new Error('Network failure'));
|
|
261
|
+
|
|
262
|
+
const result = await authService.disableTotp('123456');
|
|
263
|
+
|
|
264
|
+
expect(result).toHaveProperty('error');
|
|
265
|
+
expect(typeof (result as { error: string }).error).toBe('string');
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// ─── resetTotp ──────────────────────────────────────────────────────────
|
|
270
|
+
// Validates: Requirement 15.7 (resetTotp method)
|
|
271
|
+
|
|
272
|
+
describe('resetTotp', () => {
|
|
273
|
+
it('calls POST /user/totp/reset with { code } and returns { provisioningUri, secret } on success', async () => {
|
|
274
|
+
mockPost.mockResolvedValueOnce({
|
|
275
|
+
data: {
|
|
276
|
+
provisioningUri: 'otpauth://totp/Issuer:user@example.com?secret=NEWKEY',
|
|
277
|
+
secret: 'NEWKEY',
|
|
278
|
+
},
|
|
279
|
+
status: 200,
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const result = await authService.resetTotp('123456');
|
|
283
|
+
|
|
284
|
+
expect(mockPost).toHaveBeenCalledWith('/user/totp/reset', {
|
|
285
|
+
code: '123456',
|
|
286
|
+
});
|
|
287
|
+
expect(result).toEqual({
|
|
288
|
+
provisioningUri: 'otpauth://totp/Issuer:user@example.com?secret=NEWKEY',
|
|
289
|
+
secret: 'NEWKEY',
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('returns { error } when the backend returns a non-2xx response', async () => {
|
|
294
|
+
mockPost.mockRejectedValueOnce(
|
|
295
|
+
buildAxiosError(400, { message: 'Invalid TOTP code' }),
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
const result = await authService.resetTotp('000000');
|
|
299
|
+
|
|
300
|
+
expect(result).toEqual({ error: 'Invalid TOTP code' });
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('returns { error } when TOTP is not currently active', async () => {
|
|
304
|
+
mockPost.mockRejectedValueOnce(
|
|
305
|
+
buildAxiosError(409, {
|
|
306
|
+
message: 'TOTP is not currently active',
|
|
307
|
+
}),
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
const result = await authService.resetTotp('123456');
|
|
311
|
+
|
|
312
|
+
expect(result).toEqual({
|
|
313
|
+
error: 'TOTP is not currently active',
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('returns fallback error when a non-Axios error is thrown', async () => {
|
|
318
|
+
mockPost.mockRejectedValueOnce(new Error('Network failure'));
|
|
319
|
+
|
|
320
|
+
const result = await authService.resetTotp('123456');
|
|
321
|
+
|
|
322
|
+
expect(result).toHaveProperty('error');
|
|
323
|
+
expect(typeof (result as { error: string }).error).toBe('string');
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// ─── verifyTotpLogin ───────────────────────────────────────────────────
|
|
328
|
+
// Validates: Requirement 11.4
|
|
329
|
+
|
|
330
|
+
describe('verifyTotpLogin', () => {
|
|
331
|
+
it('calls POST /user/totp/verify with { code } and Authorization header, returns { token, user } on success', async () => {
|
|
332
|
+
const mockUser = { id: 'user-123', username: 'testuser' };
|
|
333
|
+
mockPost.mockResolvedValueOnce({
|
|
334
|
+
data: {
|
|
335
|
+
token: 'full-jwt-token',
|
|
336
|
+
user: mockUser,
|
|
337
|
+
},
|
|
338
|
+
status: 200,
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
const result = await authService.verifyTotpLogin(
|
|
342
|
+
'pending-totp-token',
|
|
343
|
+
'123456',
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
expect(mockPost).toHaveBeenCalledWith(
|
|
347
|
+
'/user/totp/verify',
|
|
348
|
+
{ code: '123456' },
|
|
349
|
+
{ headers: { Authorization: 'Bearer pending-totp-token' } },
|
|
350
|
+
);
|
|
351
|
+
expect(result).toEqual({
|
|
352
|
+
token: 'full-jwt-token',
|
|
353
|
+
user: mockUser,
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('returns { error } when the backend returns a non-2xx response', async () => {
|
|
358
|
+
mockPost.mockRejectedValueOnce(
|
|
359
|
+
buildAxiosError(400, { message: 'Invalid TOTP code' }),
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
const result = await authService.verifyTotpLogin(
|
|
363
|
+
'pending-totp-token',
|
|
364
|
+
'000000',
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
expect(result).toEqual({ error: 'Invalid TOTP code' });
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it('returns { error } when the pending token is expired', async () => {
|
|
371
|
+
mockPost.mockRejectedValueOnce(
|
|
372
|
+
buildAxiosError(401, { message: 'Token expired' }),
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
const result = await authService.verifyTotpLogin(
|
|
376
|
+
'expired-token',
|
|
377
|
+
'123456',
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
expect(result).toEqual({ error: 'Token expired' });
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it('returns { error } when the pending token is invalid', async () => {
|
|
384
|
+
mockPost.mockRejectedValueOnce(
|
|
385
|
+
buildAxiosError(401, { message: 'Invalid token' }),
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
const result = await authService.verifyTotpLogin(
|
|
389
|
+
'invalid-token',
|
|
390
|
+
'123456',
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
expect(result).toEqual({ error: 'Invalid token' });
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it('returns fallback error when a non-Axios error is thrown', async () => {
|
|
397
|
+
mockPost.mockRejectedValueOnce(new Error('Network failure'));
|
|
398
|
+
|
|
399
|
+
const result = await authService.verifyTotpLogin(
|
|
400
|
+
'pending-totp-token',
|
|
401
|
+
'123456',
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
expect(result).toHaveProperty('error');
|
|
405
|
+
expect(typeof (result as { error: string }).error).toBe('string');
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// ─── Error extraction edge cases ─────────────────────────────────────
|
|
410
|
+
// Validates: Requirement 11.5
|
|
411
|
+
|
|
412
|
+
describe('error extraction edge cases', () => {
|
|
413
|
+
it('extracts error from response.data.error when it is an object with message', async () => {
|
|
414
|
+
mockPost.mockRejectedValueOnce(
|
|
415
|
+
buildAxiosError(500, {
|
|
416
|
+
error: { message: 'Decryption failed', statusCode: 500 },
|
|
417
|
+
}),
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
const result = await authService.setupTotp();
|
|
421
|
+
|
|
422
|
+
expect(result).toEqual({ error: 'Decryption failed' });
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it('prefers error string over message when both are present', async () => {
|
|
426
|
+
mockPost.mockRejectedValueOnce(
|
|
427
|
+
buildAxiosError(400, {
|
|
428
|
+
error: 'Specific error',
|
|
429
|
+
message: 'General message',
|
|
430
|
+
}),
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
const result = await authService.confirmTotp('123456');
|
|
434
|
+
|
|
435
|
+
// extractErrorMessage checks error (string) first, then error.message, then message
|
|
436
|
+
expect(result).toEqual({ error: 'Specific error' });
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
|
|
3
|
+
export function createApiClient(baseURL: string) {
|
|
4
|
+
const api = axios.create({ baseURL });
|
|
5
|
+
|
|
6
|
+
api.interceptors.request.use((config) => {
|
|
7
|
+
const languageCode = localStorage.getItem('languageCode') ?? 'en-US';
|
|
8
|
+
config.headers['Accept-Language'] = languageCode;
|
|
9
|
+
return config;
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
return api;
|
|
13
|
+
}
|