- Clone
git clone git@github.com:trustedlogin/vendor.git
- Install javascript dependencies
yarn
- Install php dependencies
composer installdocker run --rm -it --volume "$(pwd)":/app prooph/composer:7.4 install
- Ensure
wp-scriptsis installed globallyyarn global add @wordpress/scripts
JS and CSS:
- Start CSS watcher
yarn start:css
- Start JS watcher only
yarn start:js
- Test changed files
yarn test --watch
- Test all files once
yarn testyarn test --ci
- Lint JS
yarn lint
PHP:
- Test
This local dev with Docker was built to work with ngrok, which is not what we're doing now.
Do not install in a directory that includes a space in the path, for example, one under "Local Sites". That will cause issues with wp.js.
IMPORTANT: You must use PHP 7.4 and composer 2.2+ when running composer. The wp.js script runs composer in Docker with the right versions.
- Git clone:
git clone git@github.com:trustedlogin/vendor.git
- Install javascript dependencies
yarn
- Install php dependencies
composer installdocker run --rm -it --volume "$(pwd)":/app prooph/composer:7.4 install
- Create env file
cp .env.example .env- You will need to ask Josh and/ or Zack for values for
NGROK_WP_URL,NGROK_USERS,TL_VENDOR_ENCRYTPTION_KEYandNGROK_AUTH_TOKEN.
- Setup site using
wp.jsscript.
It is important that you use the wp.js script to setup the local dev site, which is served via ngork. The e2e tests assume that site is running and was setup using this script. This script should work with Node 14 or later. Josh developed it using Node 16.
- Configure WordPress Site
node wp.js- Installs WordPress
- Creates admin users, as specified in
NGROK_USERSenv variable - Activates plugin
- Ensure that Docker is running
- Build plugin for release and ZIP
yarnnode wp.js zip
Note: if that doesn't work, make sure Docker is running and then run the following instead:
yarn build && npx --yes plugin-machine plugin build --token=notempty && npx --yes plugin-machine plugin zip --token=notempty- Activate Plugin
node wp.js --activate
- Reset WordPress
node wp.js ---reset- Drops the database tables.
The wp.js script uses docker compose.
There is a React app, for the admin page. It is located in /src. We create two different builds from this app:
- WordPress-safe JavaScript
- Built with `@wordpress/scripts.
- We use this for the admin page.
- "App Build"
- Built with
react-scripts. - We use this for the access key login link in webhooks/helpdesks.
- This build includes React and ReactDom and is not safe for use in normal WordPress screens.
- Built with
- Build all JavaScript and CSS for production
yarn build
- Build WordPress JavaScript for production
yarn build:js
- Build the "App Build" JavaScript for production
yarn build:app- This command requires that
npxis installed. npxis used to runreact-scripts, instead of installing it in this repo and usingyarn, in order to avoid potential conflicts with@wordpress/scripts.
- Start WordPress Javascript watcher and CSS watcher in parallel
yarn start- This is busted, open two tabs,
yarn start:css,yarn start:js
- Start JS watcher only
yarn start:js
- Test changed files
yarn test --watch
- Test all files once
yarn testyarn test --ci
- Lint JS
yarn lint
- Build CSS for production
yarn build:css
- Start CSS watcher for development
yarn start:css
The file admin/tailwind.css is used, with Tailwind CSS to write CSS for the admin screens. We are using Tailwind 3, with just in time compliation.
When Tailwind does its purge, it is configured to look for classes in PHP or JavaScript files in the admin directory only.
PHP classes should be located in the "php" directory and follow the PSR-4 standard.
The root namespace is TrustedLoginVendor.
$container = trustedlogin_connector();Interactions with TrustedLogin are in the TrustedLoginService. You can get this service, using the container, which it needs, like this:
$service = new TrustedLoginService(
trustedlogin_connector()
);Array of proxy REMOTE_ADDR values whose X-Forwarded-For and CF-Connecting-IP headers the plugin will trust when determining the client IP. Default: empty — no forwarded headers are honored.
This gates every use of TrustedLogin\Vendor\Utils::get_ip():
- Per-IP rate limits on
/wp-json/trustedlogin/v1/secrets/{token}fetch and/verify - The
actor_ipcolumn onwp_tl_secret_auditrows
If you run TrustedLogin Connector behind Cloudflare, a reverse proxy, or a load balancer and do not add the proxy IPs here, rate-limit buckets and audit logs will show the proxy/edge IP instead of the actual client. That's safe (attackers can't spoof out of their bucket) but can degrade forensics and cause shared rate-limit collisions between unrelated recipients.
add_filter( 'trustedlogin/connector/trusted-proxies', function ( $ips ) {
return array_merge( $ips, [
'192.0.2.10', // your reverse proxy
'192.0.2.11',
] );
} );For Cloudflare, pull the current edge IP list and expand the CIDRs to individual REMOTE_ADDR values. Only an exact string match against the incoming REMOTE_ADDR counts.
Boolean gate on per-IP rate limiting for the Secrets endpoints. Return false to disable — useful in e2e stacks or load tests where traffic from a single IP would otherwise trip the limit. Must remain true in production.
add_filter( 'trustedlogin/connector/secrets/rate-limit/enabled', '__return_false' );Overrides the effective value of the TRUSTEDLOGIN_DEBUG constant at runtime. Useful for forcing debug logging on or off in environments where the constant has already been defined and cannot be modified.
Before doing this, you must create a ".env" file in the root of this plugin. You need to set the correct value for TL_VENDOR_ENCRYTPTION_KEY. Its value is saved in Zack and Josh's password managers. It is set as a Github actions environment variable. This is not needed in production.
- Run WordPress tests
composer test- See local development instructions for how to run with Docker.
- Run e2e Tests
The integration tests mock the SASS API. The mock data was generated using encryption keys for Zack's "Test" team. This is different from the "ngrok" team used for e2e tests.
The integration tests rely on the environment variable TL_VENDOR_ENCRYTPTION_KEY:
{
/*
* private_key: (string) The private key used for encrypt/decrypt.
* public_key: (string) The public key used for encrypt/decrypt.
* sign_public_key: (string) The public key used for signing/verifying.
* sign_private_key: (string) The private key used for signing/verifying.
*/
}PHPCS is installed for linting and automatic code fixing. You can also run these commands in Docker, using wp.js.
-
Run linter and autofix
composer fixesnode wp.js fixes
-
Run linter to identify issues.
compose sniffsnode wp.js sniffs
-
Check backwards compat
compose compatnode wp.js compat
A docker-compose-based local development environment is provided.
- Start server
docker-compose up -d- If you get errors about port already being allocated, you can either:
- Kill all containers and try again:
docker kill $(docker ps -q) && docker-compose up -d - Change the port in docker-compose.yml.
- Kill all containers and try again:
- Access Site
- Run WP CLI command:
docker-compose run wpcli wp user create admin admin@example.com --role=admin user_pass=pass
- Because the constants
WP_DEBUGandTRUSTEDLOGIN_DEBUGare set to true, errors will be logged to./trustedlogin.log.- Constants are set in
docker-compose.yml, inservices.wordpress.environment, underWORDPRESS_CONFIG_EXTRA. - I'm not sure where this is documented, but it works. This Stackoverflow discussion is helpful.
- Constants are set in
There is a special phpunit container for running WordPress tests, with WordPress and MySQL configured.
- Enter container
docker-compose run phpunit
- Test
composer test:wordpress
- Run tests once
**docker-compose run phpunit phpunit --config=phpunit-integration.xml
If you see prompt Do you trust "dealerdirect/phpcodesniffer-composer-installer" to execute code and wish to enable it now? (writes "allow-plugins" to composer.json) [y,n,d,?] y you should answer "y". This is fine.
The WordPress site will also be on the internets at https://trustedlogin.ngrok.io/. This requires setting the variable NGROK_AUTH_TOKEN in the .env file.
Find the auth token in the ngrok dashboard, while logged in to the TrustedLogin account. Ask Zack for access if needed.
The ngrok container has a UI for inspecting ngrok requests. It can be accessed at http://localhost:4551/.
We use cypress for end to end testing (e2e) the vendor plugin, and the e2e client. These tests use the production eCommerce app and Vault. These tests use an "ngrok" team. Zack can add and remove people from that team.
The e2e client plugin is installed at https://e2e.trustedlogin.dev/. The e2e tests in that plugin will use that site to grant access to the "ngrok" team. We can get the accessKey from that HTTP request's response. The ngrok endpoint will be serving a WordPress site using whatever version of this plugin is being tested, served at the ngrok endpoint.
Then the tests will log into the vendor site and attempt to use the plugin's setting screen to login to the client site, using the access key.
CLIENT_WP_URL- URL Of client site:
CLIENT_WP_URL=https://e2e.trustedlogin.dev/
CLIENT_WP_PASSWORD- Password of user on client site that e2e tests login as.
CLIENT_WP_USER- Username of cf user on client site that e2e tests login as.
CLIENT_WP_USER=githubactions
NGROK_AUTH_TOKEN- The auth token for the ngrok account
- https://dashboard.ngrok.com/get-started/your-authtoken
NGROK_WP_URLNGROK_WP_URL=https://trustedlogin.ngrok.io- ngrok URL for docker compose site.
- In CI, this value should be
https://trustedlogin-ci.ngrok.io
When working on this project, you may occasionally encounter failing tests or need to add new tests for new features. Here are some steps to guide you through this process.
- Run the test: The easiest way to work on test and ensuring you code is building is to run
yarn test --watch. - Understand the test: Look at the test that is failing and understand what it is testing and what the expected behavior is.
- You are going to see an error like this:
["Warning: You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`.%s", " 1 | import React from "react"; 2 | > 3 | export const SelectFieldArea = ({ id, label, children, htmlFor }) => ( | ^ 4 | <div className=""> 5 | <label 6 | htmlFor={htmlFor ? htmlFor : id} at SelectFieldArea (src/components/teams/fields.js:3:35) at SelectField (src/components/teams/fields.js:39:31) at HelpDeskSelect (src/components/teams/EditTeam.js:14:34) at ViewProvider (src/hooks/useView.js:12:3) at SettingsProvider (src/hooks/useSettings.js:267:3) at TestProvider (src/components/TestProvider.js:33:3) at Provider"]- This tells you whats wrong and includes the stack trace where it is happening.
- Investigate the failure: Look at the error message and any stack traces that are provided. This information is usually quite helpful in pinpointing the issue.
- Fix the code: Update the code to fix the test. This could mean fixing a bug in the implementation or updating the implementation to match a new feature requirement.
- Run the test again: If running in watch mode the test will run automatically, otherwise after fixing the code, run the test again to verify that it now passes.
- Understand the requirement: Before writing the test, you should fully understand the feature you are testing and what the expected behavior is. The previous example is a test for handling the default value.
- Write the test: Using the Jest framework, write a test that checks whether the feature behaves as expected.
- The following example is a new test to verify how the application would work when the value has changed.
import { render, fireEvent } from "@testing-library/react"; it("Updates on change", () => { const handleChange = jest.fn(); const { getByLabelText } = render( <HelpDeskSelect value={"helpscout"} options={options} onChange={handleChange} />, { wrapper: Provider, } ); const select = getByLabelText(teamFields.helpdesk.label); fireEvent.change(select, { target: { value: 'new value' } }); expect(handleChange).toBeCalledWith('new value'); });- This test renders the component with a default value and then simulates a change event. It then verifies that the
onChangehandler was called with the new value.
- Run the test: Run the test to ensure it passes and the feature is working as expected.
Sometimes, a test might fail because of changes in the component structure or design, even though the component's functionality hasn't changed. If you verify that the changes are expected and not the result of a bug, you'll need to update the snapshot that Jest uses to compare in future test runs.
Here's how you update Jest snapshots:
- Run the following command in your terminal:
yarn test -- -u. - Jest will automatically update the snapshots and re-run the tests.
- Check the snapshot changes in your Git diff to ensure that all changes are as expected and intentional.
- If everything looks good, commit the updated snapshot files.
Remember, updating snapshots should be done cautiously. Ensure that you've fully reviewed and understand any snapshot changes before committing them.
Never update a snapshot if you see an unexpected change, as this might be an indication of a regression or bug in your code.