Skip to content

Commit

Permalink
Fix password field things (#5884)
Browse files Browse the repository at this point in the history
  • Loading branch information
emmatown authored Jun 9, 2021
1 parent cf8825b commit 2e3a1ec
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 2 deletions.
2 changes: 2 additions & 0 deletions docs/pages/apis/fields.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ Options:

- `isRequired` (default: `false`): If `true` then this field can never be set to `null`.
- `minLength` (default: `8`): The minimum length password accepted.
- `rejectCommon` (default: `false`): Rejects passwords from a list of commonly used passwords.
- `bcrypt` (default: `require('bcryptjs')`): A module which implements the same interface as the [`bcryptjs`](https://www.npmjs.com/package/bcryptjs) package, such as the native [`bcrypt`](https://www.npmjs.com/package/bcrypt) package.
This module will be used for all encryption routines in the `password` field.

Expand All @@ -310,6 +311,7 @@ export default config({
fieldName: password({
isRequired: true,
minLength: 10,
rejectCommon: true,
bcrypt: require('bcrypt'),
}),
/* ... */
Expand Down
17 changes: 16 additions & 1 deletion packages-next/fields/src/types/password/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
schema,
} from '@keystone-next/types';
import bcryptjs from 'bcryptjs';
// @ts-ignore
import dumbPasswords from 'dumb-passwords';
import { resolveView } from '../../resolve-view';

type PasswordFieldConfig<TGeneratedListTypes extends BaseGeneratedListTypes> =
Expand All @@ -19,6 +21,7 @@ type PasswordFieldConfig<TGeneratedListTypes extends BaseGeneratedListTypes> =
* @default 10
*/
workFactor?: number;
rejectCommon?: boolean;
bcrypt?: Pick<typeof import('bcryptjs'), 'compare' | 'compareSync' | 'hash' | 'hashSync'>;
defaultValue?: FieldDefaultValue<string, TGeneratedListTypes>;
isRequired?: boolean;
Expand All @@ -38,6 +41,7 @@ export const password =
bcrypt = bcryptjs,
minLength = 8,
workFactor = 10,
rejectCommon = false,
isRequired,
defaultValue,
...config
Expand All @@ -58,7 +62,18 @@ export const password =
return null;
}
if (typeof val === 'string') {
return bcrypt.hash(val, 10);
if (rejectCommon && dumbPasswords.check(val)) {
throw new Error(
`[password:rejectCommon:${meta.listKey}:${meta.fieldKey}] Common and frequently-used passwords are not allowed.`
);
}
if (val.length < minLength) {
throw new Error(
`[password:minLength:${meta.listKey}:${meta.fieldKey}] Value must be at least ${minLength} characters long.`
);
}

return bcrypt.hash(val, workFactor);
}
return val;
}
Expand Down
39 changes: 38 additions & 1 deletion packages-next/fields/src/types/password/tests/test-fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ export const subfieldName = 'isSet';
export const skipCreateTest = true;
export const skipUpdateTest = true;

export const getTestFields = () => ({ name: text(), password: password({ minLength: 4 }) });
export const getTestFields = () => ({
name: text(),
password: password({ minLength: 4 }),
passwordRejectCommon: password({ rejectCommon: true }),
});

export const initItems = () => {
return [
Expand All @@ -36,3 +40,36 @@ export const storedValues = () => [
];

export const supportedFilters = () => ['isSet'];

export const crudTests = (keystoneTestWrapper: any) => {
test(
'setting a password below the minLength fails',
keystoneTestWrapper(async ({ context }: { context: any }) => {
await expect(
context.lists.Test.createOne({
data: { password: '123' },
})
).rejects.toMatchInlineSnapshot(
`[GraphQLError: [password:minLength:Test:password] Value must be at least 4 characters long.]`
);
})
);
test(
'setting a common password fails',
keystoneTestWrapper(async ({ context }: { context: any }) => {
await expect(
context.lists.Test.createOne({
data: { passwordRejectCommon: 'password' },
query: ``,
})
).rejects.toMatchInlineSnapshot(
`[GraphQLError: [password:rejectCommon:Test:passwordRejectCommon] Common and frequently-used passwords are not allowed.]`
);
const data = await context.lists.Test.createOne({
data: { passwordRejectCommon: 'sdfinwedvhweqfoiuwdfnvjiewrijnf' },
query: `passwordRejectCommon {isSet}`,
});
expect(data.passwordRejectCommon.isSet).toBe(true);
})
);
};

1 comment on commit 2e3a1ec

@vercel
Copy link

@vercel vercel bot commented on 2e3a1ec Jun 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.