diff --git a/src/lib/actions/analytics.ts b/src/lib/actions/analytics.ts index 96b29d3a2..9245131ed 100644 --- a/src/lib/actions/analytics.ts +++ b/src/lib/actions/analytics.ts @@ -141,6 +141,8 @@ export enum Submit { TeamUpdateName = 'submit_team_update_name', AuthLimitUpdate = 'submit_auth_limit_update', AuthStatusUpdate = 'submit_auth_status_update', + AuthPasswordHistoryUpdate = 'submit_auth_password_history_limit_update', + AuthPasswordDictionaryUpdate = 'submit_auth_password_dictionary_update', SessionsLengthUpdate = 'submit_sessions_length_update', SessionsLimitUpdate = 'submit_sessions_limit_update', SessionDelete = 'submit_session_delete', diff --git a/src/lib/elements/forms/inputDomain.svelte b/src/lib/elements/forms/inputDomain.svelte index b261756a8..58f066199 100644 --- a/src/lib/elements/forms/inputDomain.svelte +++ b/src/lib/elements/forms/inputDomain.svelte @@ -15,7 +15,7 @@ export let maxlength: number = null; // https://www.geeksforgeeks.org/how-to-validate-a-domain-name-using-regular-expression/ - const pattern = String.raw`^(?!-)[A-Za-z0-9-]+([\-\.]{1}[a-z0-9]+)*\.[A-Za-z]{2,6}$`; + const pattern = String.raw`^(?!-)[A-Za-z0-9-]+([\-\.]{1}[a-z0-9]+)*\.[A-Za-z]{2,18}$`; let element: HTMLInputElement; let error: string; diff --git a/src/lib/layout/navigation.svelte b/src/lib/layout/navigation.svelte index bd2a9fbae..15790203b 100644 --- a/src/lib/layout/navigation.svelte +++ b/src/lib/layout/navigation.svelte @@ -163,7 +163,7 @@ {#if subNavigation} -
+
diff --git a/src/lib/layout/shell.svelte b/src/lib/layout/shell.svelte index 2e5e7819e..f4f5fd0cf 100644 --- a/src/lib/layout/shell.svelte +++ b/src/lib/layout/shell.svelte @@ -1,17 +1,17 @@ + +
+ + Password Dictionary + + + + +

+ Enabling this option prevent users from setting insecure passwords by comparing the + user's password with the 10k most commonly used passwords. +

+
+ + + + +
+
diff --git a/src/routes/console/project-[project]/auth/security/updatePasswordHistory.svelte b/src/routes/console/project-[project]/auth/security/updatePasswordHistory.svelte new file mode 100644 index 000000000..e4c48767d --- /dev/null +++ b/src/routes/console/project-[project]/auth/security/updatePasswordHistory.svelte @@ -0,0 +1,76 @@ + + +
+ + Password History + + + + +

+ Enabling this option prevents users from reusing recent passwords by comparing the + new password with their password history. +

+

+ Set the maximum number of passwords saved per user. Maximum 20 passwords. +

+ + + +
+ + + + +
+
diff --git a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/subNavigation.svelte b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/subNavigation.svelte index efb46a2e4..706d1177e 100644 --- a/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/subNavigation.svelte +++ b/src/routes/console/project-[project]/databases/database-[database]/collection-[collection]/subNavigation.svelte @@ -17,7 +17,7 @@
+ href={`${base}/console/project-${project}/databases/database-${databaseId}?openNavbar=true`}> diff --git a/tests/unit/elements/inputDomain.test.ts b/tests/unit/elements/inputDomain.test.ts new file mode 100644 index 000000000..488c03ad5 --- /dev/null +++ b/tests/unit/elements/inputDomain.test.ts @@ -0,0 +1,116 @@ +import '@testing-library/jest-dom'; +import { render } from '@testing-library/svelte'; +import userEvent from '@testing-library/user-event'; +import { InputDomain } from '../../../src/lib/elements/forms'; + +const validStrings = ['example.com', 'i-need.travelersinsurance', 'sub.domain.com']; +const invalidStrings = ['not-a-domain', 'tld-too-short.a', 'tld-too-long.overeighteencharacters']; + +test('shows domain input', () => { + const { getByText, getByLabelText } = render(InputDomain, { id: 'input', label: 'input' }); + const input = getByLabelText('input'); + + expect(getByText('input')).toBeInTheDocument(); + expect(input).toBeInTheDocument(); + expect(input).toHaveAttribute('type', 'text'); +}); + +test('shows domain input - required', () => { + const { getByLabelText } = render(InputDomain, { id: 'input', label: 'input', required: true }); + + expect(getByLabelText('input')).toBeRequired(); +}); + +test('shows domain input - disabled', () => { + const { getByLabelText } = render(InputDomain, { id: 'input', label: 'input', disabled: true }); + + expect(getByLabelText('input')).toBeDisabled(); +}); + +test('shows domain input - autofocus', () => { + const { getByLabelText } = render(InputDomain, { + id: 'input', + label: 'input', + autofocus: true + }); + + expect(getByLabelText('input')).toHaveFocus(); +}); + +test('shows domain input - placeholder', () => { + const { getByPlaceholderText } = render(InputDomain, { + id: 'input', + label: 'input', + placeholder: 'find me' + }); + + expect(getByPlaceholderText('find me')).toBeInTheDocument(); +}); + +test('shows domain input - hide label', () => { + render(InputDomain, { + id: 'input', + label: 'label', + showLabel: false + }); + + const label = document.querySelector('label'); + expect(label).toHaveClass('u-hide'); +}); + +test('shows domain input - autocomplete', () => { + const { getByLabelText } = render(InputDomain, { + id: 'input', + label: 'input', + autocomplete: true + }); + + expect(getByLabelText('input')).toHaveAttribute('autocomplete', 'on'); +}); + +test('shows domain input - maxlength', () => { + const { getByLabelText } = render(InputDomain, { + id: 'input', + label: 'input', + maxlength: 2 + }); + + expect(getByLabelText('input')).toHaveAttribute('maxlength', '2'); +}); + +test('state', async () => { + const { component, getByLabelText } = render(InputDomain, { + id: 'input', + label: 'input', + value: '' + }); + const input = getByLabelText('input'); + + expect(component.value).toEqual(''); + await userEvent.type(input, 'lorem'); + expect(component.value).toEqual('lorem'); +}); + +validStrings.forEach((validString) => { + test(`validates ${validString} as valid`, () => { + const { getByLabelText } = render(InputDomain, { + id: 'input', + label: 'input', + value: validString + }); + const input = getByLabelText('input') as HTMLInputElement; + expect(input.checkValidity()).toBe(true); + }); +}); + +invalidStrings.forEach((invalidString) => { + test(`validates ${invalidString} as invalid`, () => { + const { getByLabelText } = render(InputDomain, { + id: 'input', + label: 'input', + value: invalidString + }); + const input = getByLabelText('input') as HTMLInputElement; + expect(input.checkValidity()).toBe(false); + }); +});