{"version":3,"file":"authentication.js","sources":["../../../../../../feature/authentication/src/main/frontend/components/login/components/EcaseLogo.vue","../../../../../../feature/authentication/src/main/frontend/js/api/login.api.ts","../../../../../../feature/authentication/src/main/frontend/stores/login-properties.store.ts","../../../../../../feature/authentication/src/main/frontend/components/login/layout/LoginLayout.vue","../../../../../../feature/authentication/src/main/frontend/js/composable/google-auth-script.ts","../../../../../../feature/authentication/src/main/frontend/components/login/components/GoogleLoginButton.vue","../../../../../../feature/authentication/src/main/frontend/components/login/components/VerifyTokenConfirmation.vue","../../../../../../feature/authentication/src/main/frontend/js/api/password.api.ts","../../../../../../feature/authentication/src/main/frontend/components/login/page/LoginPage.vue","../../../../../../feature/authentication/src/main/frontend/js/enum/time-unit.ts","../../../../../../feature/authentication/src/main/frontend/js/api/user-authentication-settings.api.ts","../../../../../../feature/authentication/src/main/frontend/components/login/page/VerifyPage.vue","../../../../../../feature/authentication/src/main/frontend/js/composable/google-recaptcha-script.ts","../../../../../../feature/authentication/src/main/frontend/components/login/components/GoogleReCaptchaCheck.vue","../../../../../../feature/authentication/src/main/frontend/components/login/page/ForgotPasswordPage.vue","../../../../../../feature/authentication/src/main/frontend/components/login/page/NotFoundPage.vue","../../../../../../feature/authentication/src/main/frontend/js/composable/new-password.ts","../../../../../../feature/authentication/src/main/frontend/components/login/page/ResetPasswordPage.vue","../../../../../../feature/authentication/src/main/frontend/authentication.router.js","../../../../../../feature/authentication/src/main/frontend/js/util/user-authentication-settings-util.ts","../../../../../../feature/authentication/src/main/frontend/pages/UserAuthenticationSettingsPage.vue","../../../../../../feature/authentication/src/main/frontend/components/ipwhitelist/IpWhitelist.vue","../../../../../../feature/authentication/src/main/frontend/js/api/saml-settings.api.ts","../../../../../../feature/authentication/src/main/frontend/components/sso/SamlSettings.vue","../../../../../../feature/authentication/src/main/frontend/authentication.app.js"],"sourcesContent":["\n\n\n\n\n","import { ajaxGet, ajaxPost } from \"core/js/spring-ajax\";\n\nconst LOGIN_CREDENTIALS_URL = \"/authentication/rest/login/credentials\";\nconst LOGIN_TOKEN_URL = \"/authentication/rest/login/token\";\nconst LOGIN_GOOGLE_URL = \"/authentication/rest/login/google\";\nconst LOGIN_PROPERTIES_URL = \"/authentication/rest/login/properties\";\n\nexport const SSO_LOGIN_PATH = \"/authentication/sso/saml/initiate\";\n\nexport type LoginStatus = \"SUCCESS\" | \"FAILURE\" | \"TOKEN_VERIFICATION_REQUIRED\";\n\nexport type FailureReason = \"INVALID_CREDENTIALS\" | \"INVALID_TOKEN\" | \"OTHER\";\n\ninterface LoginFailure {\n failureReason: FailureReason\n message?: string;\n}\n\nexport interface LoginResult {\n loginStatus: LoginStatus;\n loginFailure?: LoginFailure;\n throttleTimeoutSeconds?: number;\n redirectUrl?: string\n}\n\nexport interface LoginProperties {\n systemAreaName: string;\n samlEnabled: boolean;\n ipWhitelistEnabled: boolean;\n forgotPasswordUrl: string;\n registrationUrl?: string;\n systemMessageHtml?: string;\n googleAuthClientId?: string;\n}\n\nexport async function loginWithCredentials(username: string, password: string): Promise {\n return ajaxPost({\n url: LOGIN_CREDENTIALS_URL,\n data: { username, password },\n });\n}\n\nexport async function loginWithToken(token: string): Promise {\n return ajaxPost({\n url: `${LOGIN_TOKEN_URL}?token=${token}`,\n data: {},\n });\n}\n\nexport async function loginWithGoogle(idToken: string): Promise {\n return ajaxPost({\n url: `${LOGIN_GOOGLE_URL}?idToken=${idToken}`,\n data: {},\n });\n}\n\nexport async function getLoginProperties(): Promise {\n return ajaxGet({ url: LOGIN_PROPERTIES_URL });\n}\n","import { defineStore } from \"pinia\";\nimport { computed, Ref, ref } from \"vue\";\nimport { debounceAction } from \"core/store/store-utils\";\nimport { getLoginProperties, LoginProperties } from \"authentication/js/api/login.api\";\n\nexport const useLoginPropertiesStore = defineStore(\"feature/authentication/login-properties\", () => {\n const loginProperties = ref({}) as Ref;\n\n const multipleLoginOptionsExist = computed(() => loginProperties.value.samlEnabled || loginProperties.value.googleAuthClientId);\n\n async function fetchLoginProperties() {\n if (loginProperties.value && Object.keys(loginProperties.value).length) {\n return;\n }\n\n loginProperties.value = await getLoginProperties();\n }\n\n return {\n loginProperties,\n multipleLoginOptionsExist,\n fetchLoginProperties: debounceAction(fetchLoginProperties),\n };\n});\n","\n\n\n\n\n","export const TimeUnits = [\"MINUTES\", \"HOURS\"] as const;\n\nexport type TimeUnit = typeof TimeUnits[number];\n\nexport function getTimeUnitOptions() {\n return TimeUnits.map(timeUnit => ({\n id: timeUnit,\n displayText: getTimeUnitDisplayText(timeUnit),\n }));\n}\n\nfunction getTimeUnitDisplayText(timeUnit: TimeUnit) {\n switch (timeUnit) {\n case \"MINUTES\":\n return \"Minutes\";\n case \"HOURS\":\n return \"Hours\";\n }\n}\n","import { ajaxGet, ajaxPut } from \"core/js/spring-ajax\";\nimport { TimeUnit } from \"authentication/js/enum/time-unit\";\n\nconst USER_AUTHENTICATION_SETTINGS_ENDPOINT = \"/authentication/rest/api/settings/user-authentication\";\nconst MFA_SETTINGS_ENDPOINT = \"/authentication/rest/api/settings/user-authentication/mfa-settings\";\n\nexport interface UserAuthenticationSettings {\n timeoutSettings: SessionTimeoutSettings,\n mfaSettings?: MfaSettings,\n}\n\nexport interface MfaSettings {\n durationDays?: string,\n}\n\nexport interface SessionTimeoutSettings {\n duration: string,\n timeUnit: TimeUnit,\n}\n\nexport interface UserAuthenticationSettingsValidationErrors {\n timeoutSettings: SessionTimeoutSettingsValidationErrors\n mfaSettings: MfaSettingsValidationErrors;\n}\n\nexport interface SessionTimeoutSettingsValidationErrors {\n duration?: string,\n timeUnit?: string,\n}\n\nexport interface MfaSettingsValidationErrors {\n durationDays?: string,\n}\n\nexport async function getUserAuthenticationSettings(): Promise {\n const response = await ajaxGet({ url: USER_AUTHENTICATION_SETTINGS_ENDPOINT });\n return {\n timeoutSettings: {\n duration: response.timeoutSettings.duration.toString(),\n timeUnit: response.timeoutSettings.timeUnit,\n },\n mfaSettings: {\n durationDays: response.mfaSettings?.durationDays,\n },\n };\n}\n\nexport async function saveUserAuthenticationSettings(userAuthenticationSettings: UserAuthenticationSettings) {\n await ajaxPut({\n url: USER_AUTHENTICATION_SETTINGS_ENDPOINT,\n data: {\n timeoutSettings: {\n duration: +userAuthenticationSettings.timeoutSettings.duration,\n timeUnit: userAuthenticationSettings.timeoutSettings.timeUnit,\n },\n mfaSettings: {\n durationDays: userAuthenticationSettings.mfaSettings?.durationDays !== undefined\n ? Number(userAuthenticationSettings.mfaSettings.durationDays)\n : null,\n },\n },\n });\n}\n\nexport async function getMfaSettings(): Promise {\n const response = await ajaxGet({ url: MFA_SETTINGS_ENDPOINT });\n return {\n durationDays: response.durationDays,\n };\n}","\n\n\n\n","import { ref, nextTick } from \"vue\";\nimport showFlashMessage from \"core/js/flash-message\";\n\nconst GOOGLE_RECAPTCHA_CLIENT_LIBRARY_SRC = \"https://www.google.com/recaptcha/api.js\";\n\nexport function useGoogleReCaptchaScript(\n handleReCaptchaResponse: (response: string) => void,\n handleReCaptchaExpired: () => void,\n) {\n const reCaptchaSiteKey = window.ECASE.ReCaptchaSiteKey;\n const reCaptchaWrapperId = \"googleRecaptchaWidgetWrapper\";\n const globalReCaptchaResponseCallbackFunction = \"ECASE_reCaptchaResponseCallback\";\n const globalReCaptchaExpiredCallbackFunction = \"ECASE_reCaptchaExpiredCallback\";\n const reCaptchaLibraryLoaded = ref(false);\n\n function handleScriptError(error: string | Event) {\n showFlashMessage(\n \"There was an error in loading the reCAPTCHA widget. \"\n + \"Please contact your local support desk if this issue persists.\",\n { displayType: \"danger\" },\n );\n\n throw new Error(typeof error === \"string\" ? error : error.type);\n }\n\n function onScriptLoaded() {\n reCaptchaLibraryLoaded.value = true;\n }\n\n async function loadGoogleReCaptchaScript() {\n if (!window.grecaptcha) {\n // The reCAPTCHA client library JS loads another bit of JS, which adds another script tag to the document that actually\n // loads the core library code. So there is no sufficient means in detecting once *all* of the code has loaded.\n // So instead of programmatically rendering the widget, the reCAPTCHA vue component will use an HTML element that\n // automatically shows the widget once the required code has loaded. As part of this, that HTML element will need\n // a global reference to the JS function for handling the reCAPTCHA callback. This function cannot be scoped to any\n // of the Vue code, so will have to use a window function that calls the required Vue function. Same for expired callback.\n\n // eslint-disable-next-line\n (window as any)[globalReCaptchaResponseCallbackFunction] = (recaptchaResponseToken: string) => { // @ts-ignore\n handleReCaptchaResponse(recaptchaResponseToken);\n };\n\n // eslint-disable-next-line\n (window as any)[globalReCaptchaExpiredCallbackFunction] = () => { // @ts-ignore\n handleReCaptchaExpired();\n };\n\n const script = document.createElement(\"script\");\n script.src = GOOGLE_RECAPTCHA_CLIENT_LIBRARY_SRC;\n script.onload = onScriptLoaded;\n script.onerror = handleScriptError;\n script.async = true;\n\n document.head.appendChild(script);\n } else {\n // This means the script has already loaded and widget was rendered, but user has navigated away from the page and\n // returned back to it again, so must manually re-render the widget\n onScriptLoaded();\n\n // Ensure widget wrapper is visible on screen\n await nextTick();\n\n // Programmatically render the widget\n window.grecaptcha.render(reCaptchaWrapperId, {\n sitekey: reCaptchaSiteKey,\n callback: handleReCaptchaResponse,\n \"expired-callback\": handleReCaptchaExpired,\n });\n }\n }\n\n return {\n reCaptchaSiteKey,\n reCaptchaWrapperId,\n globalReCaptchaResponseCallbackFunction,\n globalReCaptchaExpiredCallbackFunction,\n reCaptchaLibraryLoaded,\n\n loadGoogleReCaptchaScript,\n };\n}\n","\n\n\n\n\n","\n\n\n","\n\n\n\n","import { computed, ref } from \"vue\";\n\nexport function useNewPassword() {\n const password = ref(\"\");\n const passwordConfirmation = ref(\"\");\n\n const isMinimumEightCharacters = computed(() => password.value.length >= 8);\n const hasAtLeastOneLowerCaseCharacter = computed(() => /[a-z]/.test(password.value));\n const hasAtLeastOneUpperCaseCharacter = computed(() => /[A-Z]/.test(password.value));\n const hasAtLeastOneNumber = computed(() => /\\d/.test(password.value));\n const hasAtLeastOneSpecialCharacter = computed(() => /[!@#$%^&*()\\\\_=+{};:,<.>-]/.test(password.value));\n const passwordsMatch = computed(() => password.value === passwordConfirmation.value);\n\n function getDisplayClasses(rule: boolean) {\n return [\"ecase-sign-in__form-password-policy-list-item\", rule ? \"ecase-sign-in__form-password-policy-list-item--valid\" : \"\"];\n }\n\n return {\n password,\n passwordConfirmation,\n\n isMinimumEightCharacters,\n hasAtLeastOneLowerCaseCharacter,\n hasAtLeastOneUpperCaseCharacter,\n hasAtLeastOneNumber,\n hasAtLeastOneSpecialCharacter,\n passwordsMatch,\n\n getDisplayClasses,\n };\n}","\n\n\n","import { createRouter, createWebHistory } from \"vue-router\";\nimport springUrl from \"core/js/spring-url.js\";\nimport { actionStart } from \"core/js/load-tracker.js\";\nimport LoginPage from \"authentication/components/login/page/LoginPage.vue\";\nimport VerifyPage from \"authentication/components/login/page/VerifyPage.vue\";\nimport ForgotPasswordPage from \"authentication/components/login/page/ForgotPasswordPage.vue\";\nimport NotFoundPage from \"authentication/components/login/page/NotFoundPage.vue\";\nimport ResetPasswordPage from \"authentication/components/login/page/ResetPasswordPage.vue\";\n\nexport const routes = [\n {\n path: springUrl(\"authentication/login\"),\n name: \"login-page\",\n component: LoginPage,\n meta: { documentTitle: \"Sign in\" },\n },\n {\n path: springUrl(\"authentication/login/verify\"),\n name: \"verify-page\",\n component: VerifyPage,\n meta: { documentTitle: \"Verify your identity\" },\n },\n {\n path: springUrl(\"authentication/login/forgot-password\"),\n name: \"forgot-password-page\",\n component: ForgotPasswordPage,\n meta: { documentTitle: \"Request password reset\" },\n },\n {\n path: springUrl(\"authentication/login/reset-password\"),\n name: \"reset-password-page\",\n component: ResetPasswordPage,\n meta: { documentTitle: \"Reset your password\" },\n },\n {\n path: \"/:pathMatch(.*)*\",\n component: NotFoundPage,\n },\n];\n\nconst router = createRouter({\n history: createWebHistory(),\n routes,\n});\n\nrouter.beforeEach((to, from, next) => {\n if (to.meta?.documentTitle) {\n document.title = `${to.meta.documentTitle} - eCase`;\n }\n\n actionStart(to.name);\n next();\n});\n\nexport default router;","import {\n UserAuthenticationSettings,\n UserAuthenticationSettingsValidationErrors,\n} from \"authentication/js/api/user-authentication-settings.api\";\n\nexport function validateUserAuthenticationSettings(\n userAuthenticationSettings: UserAuthenticationSettings,\n isMfaConfigOptionEnabled: boolean,\n): UserAuthenticationSettingsValidationErrors {\n const validationErrors = {\n timeoutSettings: { duration: \"\" },\n mfaSettings: { durationDays: \"\" },\n } as UserAuthenticationSettingsValidationErrors;\n if (!Number.isSafeInteger(+userAuthenticationSettings.timeoutSettings.duration)) {\n validationErrors.timeoutSettings.duration = \"Session duration must be a valid whole number\";\n }\n if (isMfaConfigOptionEnabled) {\n if (userAuthenticationSettings.mfaSettings?.durationDays === undefined\n || !Number.isSafeInteger(+userAuthenticationSettings.mfaSettings.durationDays)) {\n validationErrors.mfaSettings.durationDays = \"MFA duration must be a valid whole number of days\";\n }\n }\n return validationErrors;\n}","\n\n","\n\n\n","import { AjaxError, ajaxGet, ajaxPut } from \"core/js/spring-ajax\";\n\nconst SAML_SETTINGS_API_URL = \"/authentication/rest/api/settings/sso/saml\";\n\nexport interface SamlSettings {\n enabled: boolean,\n idpUrl: string,\n idpEntityId: string,\n primaryX509Certificate: string,\n secondaryX509Certificate: string,\n spEntityId: string,\n}\n\nexport interface SamlSettingsSaveResult {\n valid: boolean,\n validationErrors: object,\n}\n\nexport async function getSamlSettings(): Promise {\n return ajaxGet({ url: SAML_SETTINGS_API_URL });\n}\n\nexport async function saveSamlSettingsToUrl(\n samlSettings: SamlSettings,\n saveUrl: string,\n): Promise {\n try {\n await ajaxPut({ url: saveUrl, data: samlSettings });\n return { valid: true, validationErrors: {} };\n } catch (error) {\n const ajaxError = error as AjaxError;\n if (ajaxError.status === 422) {\n return { valid: false, validationErrors: { ...ajaxError.responseJSON } };\n } else {\n throw error;\n }\n }\n}\n\nexport async function saveSamlSettings(\n samlSettings: SamlSettings,\n): Promise {\n return saveSamlSettingsToUrl(samlSettings, SAML_SETTINGS_API_URL);\n}","