diff --git a/.changeset/cold-deers-drive.md b/.changeset/cold-deers-drive.md new file mode 100644 index 000000000..c6eeff145 --- /dev/null +++ b/.changeset/cold-deers-drive.md @@ -0,0 +1,47 @@ +--- +"@logto/schemas": minor +"@logto/console": minor +"@logto/core": minor +"@logto/phrases": minor +"@logto/phrases-ui": minor +"@logto/core-kit": minor +"@logto/ui": minor +--- + +feature: password policy + +### Summary + +This feature enables custom password policy for users. Now it is possible to guard with the following rules when a user is creating a new password: + +- Minimum length (default: `8`) +- Minimum character types (default: `1`) +- If the password has been pwned (default: `true`) +- If the password is exactly the same as or made up of the restricted phrases: + - Repetitive or sequential characters (default: `true`) + - User information (default: `true`) + - Custom words (default: `[]`) + +If you are an existing Logto Cloud user or upgrading from a previous version, to ensure a smooth experience, we'll keep the original policy as much as possible: + +> The original password policy requires a minimum length of 8 and at least 2 character types (letters, numbers, and symbols). + +Note in the new policy implementation, it is not possible to combine lower and upper case letters into one character type. So the original password policy will be translated into the following: + +- Minimum length: `8` +- Minimum character types: `2` +- Pwned: `false` +- Repetitive or sequential characters: `false` +- User information: `false` +- Custom words: `[]` + +If you want to change the policy, you can do it: + +- Logto Console -> Sign-in experience -> Password policy. +- Update `passwordPolicy` property in the sign-in experience via Management API. + +### Side effects + +- All new users will be affected by the new policy immediately. +- Existing users will not be affected by the new policy until they change their password. +- We removed password restrictions when adding or updating a user via Management API. diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index aac4ae4c7..c3170c634 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -148,6 +148,7 @@ jobs: working-directory: ./fresh run: pnpm cli db alt deploy next env: + ALTERATION_TEST: true DB_URL: postgres://postgres:postgres@localhost:5432/alteration - name: Compare databases @@ -168,6 +169,7 @@ jobs: working-directory: ./fresh run: pnpm cli db alt r v$(echo ${{ steps.version.outputs.current }} | cut -d@ -f3) env: + ALTERATION_TEST: true DB_URL: postgres://postgres:postgres@localhost:5432/fresh - name: Compare manifests diff --git a/packages/schemas/alterations/next-1694509714-keep-existing-password-policy.ts b/packages/schemas/alterations/next-1694509714-keep-existing-password-policy.ts new file mode 100644 index 000000000..480f9b79d --- /dev/null +++ b/packages/schemas/alterations/next-1694509714-keep-existing-password-policy.ts @@ -0,0 +1,54 @@ +import { yes } from '@silverhand/essentials'; +import { sql } from 'slonik'; + +import type { AlterationScript } from '../lib/types/alteration.js'; + +// In the alteration testing environment, we do not want to run this alteration +// script since it alters the existing data which does not match the new policy. +const isAlterationTesting = yes(process.env.ALTERATION_TEST); + +/** + * Note: The legacy password policy does not separate upper and lower cases into + * different character types. It is not possible to migrate this behavior. + */ +const legacyPasswordPolicy = { + length: { min: 8 }, + characterTypes: { min: 2 }, + rejects: { + pwned: false, + repetitionAndSequence: false, + userInfo: false, + words: [], + }, +}; + +const alteration: AlterationScript = { + up: async (pool) => { + if (isAlterationTesting) { + console.warn( + 'Skipping alteration script next-1694509714-keep-existing-password-policy in alteration testing environment.' + ); + return; + } + + await pool.query(sql` + update sign_in_experiences + set password_policy = ${sql.jsonb(legacyPasswordPolicy)}; + `); + }, + down: async (pool) => { + if (isAlterationTesting) { + console.warn( + 'Skipping alteration script next-1694509714-keep-existing-password-policy in alteration testing environment.' + ); + return; + } + + await pool.query(sql` + update sign_in_experiences + set password_policy = '{}'::jsonb; + `); + }, +}; + +export default alteration;