You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
So @TomDo1234 and I were talking on the Payload Discord about a new feature that would globally set a maxLength validation constraint for all text fields / etc. across Payload, so as to prevent against brute-force attacks where a hostile user might try and send in massively large strings against lots of text / number fields / etc.
This new global protection property could be called defaultMaxLength or similar and it could be placed on the top-level Payload config. It should have a default value, set internally by Payload, but should be able to be overridden by developers building with Payload.
This is a great idea and could be a good PR for someone coming into the community to contribute. So I thought I'd document the steps as to what I'd do here and how to go about PRing this feature.
Step 1
For anyone trying to contribute to Payload, first step is to read our CONTRIBUTING.md overview, which discusses lots of things including how our testing suite works. We have a pretty cool testing infrastructure if I do say so myself and it makes building new features, and even doing test-driven development, pretty fun.
Step 2
From there, you can fork Payload and then create a branch. Our branch names are prefixed with one of the following:
chore/
fix/
feat/
Choose the prefix depending on what you're trying to do and then create your branch, with the prefix, and with a good name that reflects your PR intent.
Commit naming conventions
We use Conventional Commits for all commit names, and you should too. Basically, the TL;DR for most use cases is to prefix your commits with either fix: , feat: , chore: , etc. and then your commits will automatically show up in our changelog when we publish a release with your PR.
Step 3
Now it's time to figure out what testing suite you'll be working inside of as you build your PR. We have an assortment of pre-existing test suites already set up, and most of the time, it's suggested to work within an existing test suite. You can see a list of all our test suites here. They are organized by feature type, and each test suite typically includes a custom Payload config, an int.spec.ts file for integration tests (tests that only test the Payload APIs - not frontend) and then an e2e.spec.ts file which uses Playwright to test admin UI if you're changing / working on stuff within the admin UI that should be tested.
So what we'll do is basically read through the existing test suites and find one that seems appropriate for what we're trying to do. Off the bat, I'd say this feature belongs within the existing fields test suite and we could work in a few tests into our existing Text collection.
Step 4
Now that we've got all the above out of the way, it's time to build.
The first step in adding a new config property is to add the property to the Typescript config type and schema validator, which are both located in /src/config. Make sure the type and the schema both reflect the new property.
The next step is to "sanitize" the incoming config. This is where we add default values, ensure that the config is valid via logic that is more complex than what joi can validate, and more. In our case, we just want to add a default value to our new defaultMaxLength property - so we can add it into the src/config/defaults.ts file, and these defaults will be automatically merged into an incoming Payload config.
Then from there, we will want to use this new feature within each applicable default field validate function.
All of the default validate functions accept arguments out of the box. However, the validate functions are shared on both the server and the admin UI, and that means that their arguments differ slightly based on if the function is called on the server or the client.
Here are the built-in types for validate functions:
As you can see above, ValidateOptions exposes payload as an optional property. It will be passed on the server, but not in the browser, as Payload and its local API can only be used on the server.
So let's say that we want to extend the built-in Text field validator to rely on our newly added defaultMaxLength property.
Here's the existing text field validate function:
exportconsttext: Validate<unknown,unknown,TextField>=(value: string,{ minLength, maxLength, required })=>{if(value&&maxLength&&value.length>maxLength){return`This value must be shorter than the max length of ${maxLength} characters.`;}if(value&&minLength&&value?.length<minLength){return`This value must be longer than the minimum length of ${minLength} characters.`;}if(required){if(typeofvalue!=='string'||value?.length===0){returndefaultMessage;}}returntrue;};
A quick example for how we might extend this field is to do something like the following:
exportconsttext: Validate<unknown,unknown,TextField>=(value: string,{ minLength,maxLength: fieldMaxLength, required, payload })=>{letmaxlength: number;if(typeofpayload?.config?.defaultMaxLength==='number')maxLength=payload.config.defaultMaxLength;if(typeoffieldMaxLength==='number')maxlength=fieldMaxLength;if(value&&maxLength&&value.length>maxLength){return`This value must be shorter than the max length of ${maxLength} characters.`;}if(value&&minLength&&value?.length<minLength){return`This value must be longer than the minimum length of ${minLength} characters.`;}if(required){if(typeofvalue!=='string'||value?.length===0){returndefaultMessage;}}returntrue;};
We've done a few simple things:
Renamed the incoming field property's maxLength to fieldMaxLength for clarity
Destructured payload from the ValidateOptions args
Added a check to see if payload?.config?.defaultMaxLength exists in the function. If it does, set maxLength equal to it. Note that this will only happen on the server, because payload will not exist in the browser UI.
Added a check to see if the fieldMaxLength is set. If it is, it should override the default
Then from there, the rest of the validate function's logic will work just the same!
Now it's time to make this clean and apply it to the other applicable validate functions.
Step 5
After the logic is in place, it's time to test. This seems appropriate for an integration test, because it will have no changes in the admin UI. So we'll open up the int tests from the test suite that we found, test/fields, and then add a test within the text test block. Maybe we just generate a huge string and then try to create a new document in the text collection.
We would assert that the newly created doc should fail and return an error on the field. To run tests, you can run yarn test to run all test suites, or in our case, as we are only adjusting integration tests, you can run yarn test:int.
Step 6
Next step is to update the docs. Payload's docs are stored directly in the Payload repo under the /docs folder, and because we've added a property to the base Payload config, we need to document it. That would go in the configuration/overview section.
Step 7
Now it's time to commit (following conventional commits), push, and open the PR! Done!
Conclusion
We'll be dramatically upping our game in regards to "good first issues", etc. over the next few months, but hopefully this guide gives a good overview of what it's like to make a PR against Payload. Thank you!
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
So @TomDo1234 and I were talking on the Payload Discord about a new feature that would globally set a
maxLength
validation constraint for all text fields / etc. across Payload, so as to prevent against brute-force attacks where a hostile user might try and send in massively large strings against lots of text / number fields / etc.This new global protection property could be called
defaultMaxLength
or similar and it could be placed on the top-level Payload config. It should have a default value, set internally by Payload, but should be able to be overridden by developers building with Payload.This is a great idea and could be a good PR for someone coming into the community to contribute. So I thought I'd document the steps as to what I'd do here and how to go about PRing this feature.
Step 1
For anyone trying to contribute to Payload, first step is to read our
CONTRIBUTING.md
overview, which discusses lots of things including how our testing suite works. We have a pretty cool testing infrastructure if I do say so myself and it makes building new features, and even doing test-driven development, pretty fun.Step 2
From there, you can fork Payload and then create a branch. Our branch names are prefixed with one of the following:
chore/
fix/
feat/
Choose the prefix depending on what you're trying to do and then create your branch, with the prefix, and with a good name that reflects your PR intent.
Commit naming conventions
We use Conventional Commits for all commit names, and you should too. Basically, the TL;DR for most use cases is to prefix your commits with either
fix:
,feat:
,chore:
, etc. and then your commits will automatically show up in our changelog when we publish a release with your PR.Step 3
Now it's time to figure out what testing suite you'll be working inside of as you build your PR. We have an assortment of pre-existing test suites already set up, and most of the time, it's suggested to work within an existing test suite. You can see a list of all our test suites here. They are organized by feature type, and each test suite typically includes a custom Payload config, an
int.spec.ts
file for integration tests (tests that only test the Payload APIs - not frontend) and then ane2e.spec.ts
file which uses Playwright to test admin UI if you're changing / working on stuff within the admin UI that should be tested.So what we'll do is basically read through the existing test suites and find one that seems appropriate for what we're trying to do. Off the bat, I'd say this feature belongs within the existing
fields
test suite and we could work in a few tests into our existingText
collection.Step 4
Now that we've got all the above out of the way, it's time to build.
The first step in adding a new config property is to add the property to the Typescript config type and schema validator, which are both located in
/src/config
. Make sure the type and the schema both reflect the new property.The next step is to "sanitize" the incoming config. This is where we add default values, ensure that the config is valid via logic that is more complex than what
joi
can validate, and more. In our case, we just want to add a default value to our newdefaultMaxLength
property - so we can add it into thesrc/config/defaults.ts
file, and these defaults will be automatically merged into an incoming Payload config.Then from there, we will want to use this new feature within each applicable default field
validate
function.All of the default
validate
functions accept arguments out of the box. However, thevalidate
functions are shared on both the server and the admin UI, and that means that their arguments differ slightly based on if the function is called on the server or the client.Here are the built-in types for
validate
functions:As you can see above,
ValidateOptions
exposespayload
as an optional property. It will be passed on the server, but not in the browser, as Payload and its local API can only be used on the server.So let's say that we want to extend the built-in Text field validator to rely on our newly added
defaultMaxLength
property.Here's the existing
text
field validate function:A quick example for how we might extend this field is to do something like the following:
We've done a few simple things:
maxLength
tofieldMaxLength
for claritypayload
from theValidateOptions
argspayload?.config?.defaultMaxLength
exists in the function. If it does, setmaxLength
equal to it. Note that this will only happen on the server, becausepayload
will not exist in the browser UI.fieldMaxLength
is set. If it is, it should override the defaultThen from there, the rest of the validate function's logic will work just the same!
Now it's time to make this clean and apply it to the other applicable
validate
functions.Step 5
After the logic is in place, it's time to test. This seems appropriate for an integration test, because it will have no changes in the admin UI. So we'll open up the
int
tests from the test suite that we found,test/fields
, and then add a test within thetext
test block. Maybe we just generate a huge string and then try to create a new document in thetext
collection.We would assert that the newly created doc should fail and return an error on the field. To run tests, you can run
yarn test
to run all test suites, or in our case, as we are only adjusting integration tests, you can runyarn test:int
.Step 6
Next step is to update the docs. Payload's docs are stored directly in the Payload repo under the
/docs
folder, and because we've added a property to the base Payload config, we need to document it. That would go in the configuration/overview section.Step 7
Now it's time to commit (following conventional commits), push, and open the PR! Done!
Conclusion
We'll be dramatically upping our game in regards to "good first issues", etc. over the next few months, but hopefully this guide gives a good overview of what it's like to make a PR against Payload. Thank you!
Beta Was this translation helpful? Give feedback.
All reactions