From e6ee1b7baef1d7f206209523f166e9d2392b9884 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Wed, 11 Sep 2024 16:03:56 +0200 Subject: [PATCH 1/8] Add cookie strategy to PHP request handler configuration --- .../universal/src/lib/php-request-handler.ts | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/packages/php-wasm/universal/src/lib/php-request-handler.ts b/packages/php-wasm/universal/src/lib/php-request-handler.ts index 6fde8a2842..ff3ce41085 100644 --- a/packages/php-wasm/universal/src/lib/php-request-handler.ts +++ b/packages/php-wasm/universal/src/lib/php-request-handler.ts @@ -95,7 +95,20 @@ export type PHPRequestHandlerConfiguration = BaseConfiguration & */ maxPhpInstances?: number; } - ); + ) & { + /** + * - `internal-store`: Persist cookies from reponses in an internal store and + * includen them in following requests. This is a behavior mostly needed when + * using Playground in web. + * + * - `pass-through`: Avoid persisting cookies internally and let them pass + * through the requests and back to the caller from responses. This is the + * common behavior in requests handled by a server. + * + * Default value is `internal-store`. + */ + cookieStrategy?: 'internal-store' | 'pass-through'; + }; /** * Handles HTTP requests using PHP runtime as a backend. @@ -159,7 +172,7 @@ export class PHPRequestHandler { #HOST: string; #PATHNAME: string; #ABSOLUTE_URL: string; - #cookieStore: HttpCookieStore; + #cookieStore?: HttpCookieStore; rewriteRules: RewriteRule[]; processManager: PHPProcessManager; getFileNotFoundAction: FileNotFoundGetActionCallback; @@ -198,7 +211,10 @@ export class PHPRequestHandler { maxPhpInstances: config.maxPhpInstances, }); } - this.#cookieStore = new HttpCookieStore(); + const cookieStrategy = config.cookieStrategy ?? 'internal-store'; + if (cookieStrategy === 'internal-store') { + this.#cookieStore = new HttpCookieStore(); + } this.#DOCROOT = documentRoot; const url = new URL(absoluteUrl); @@ -492,9 +508,12 @@ export class PHPRequestHandler { const headers: Record = { host: this.#HOST, ...normalizeHeaders(request.headers || {}), - cookie: this.#cookieStore.getCookieRequestHeader(), }; + if (this.#cookieStore) { + headers['cookie'] = this.#cookieStore.getCookieRequestHeader(); + } + let body = request.body; if (typeof body === 'object' && !(body instanceof Uint8Array)) { preferredMethod = 'POST'; @@ -522,7 +541,7 @@ export class PHPRequestHandler { scriptPath, headers, }); - this.#cookieStore.rememberCookiesFromResponseHeaders( + this.#cookieStore?.rememberCookiesFromResponseHeaders( response.headers ); return response; From 934d1be5d5fc3c51caa64749565f6c8dd13c4884 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Wed, 11 Sep 2024 16:04:09 +0200 Subject: [PATCH 2/8] Add unit tests to cover cookie strategy logic --- .../node/src/test/php-request-handler.spec.ts | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/packages/php-wasm/node/src/test/php-request-handler.spec.ts b/packages/php-wasm/node/src/test/php-request-handler.spec.ts index 5710b5435e..368fba5d5d 100644 --- a/packages/php-wasm/node/src/test/php-request-handler.spec.ts +++ b/packages/php-wasm/node/src/test/php-request-handler.spec.ts @@ -765,3 +765,84 @@ describe('PHPRequestHandler – Loopback call', () => { expect(response.text).toEqual('Starting: Ran second.php! Done'); }); }); + +describe('PHPRequestHandler – Cookie strategy', () => { + it('should persist cookies internally with internal-store strategy', async () => { + const handler = new PHPRequestHandler({ + documentRoot: '/', + phpFactory: async () => + new PHP(await loadNodeRuntime(RecommendedPHPVersion)), + maxPhpInstances: 1, + }); + const php = await handler.getPrimaryPhp(); + + php.writeFile( + '/set-cookie.php', + ` { + const handler = new PHPRequestHandler({ + documentRoot: '/', + phpFactory: async () => + new PHP(await loadNodeRuntime(RecommendedPHPVersion)), + maxPhpInstances: 1, + cookieStrategy: 'pass-through', + }); + const php = await handler.getPrimaryPhp(); + + php.writeFile( + '/set-cookie.php', + ` Date: Wed, 11 Sep 2024 16:45:05 +0200 Subject: [PATCH 3/8] Add cookie strategy to boot function --- packages/php-wasm/universal/src/lib/index.ts | 1 + .../universal/src/lib/php-request-handler.ts | 4 +++- packages/playground/wordpress/src/boot.ts | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/php-wasm/universal/src/lib/index.ts b/packages/php-wasm/universal/src/lib/index.ts index bb5ec4531a..767a8437b1 100644 --- a/packages/php-wasm/universal/src/lib/index.ts +++ b/packages/php-wasm/universal/src/lib/index.ts @@ -60,6 +60,7 @@ export type { } from './load-php-runtime'; export type { + CookieStrategy, PHPRequestHandlerConfiguration, RewriteRule, } from './php-request-handler'; diff --git a/packages/php-wasm/universal/src/lib/php-request-handler.ts b/packages/php-wasm/universal/src/lib/php-request-handler.ts index ff3ce41085..30666f2004 100644 --- a/packages/php-wasm/universal/src/lib/php-request-handler.ts +++ b/packages/php-wasm/universal/src/lib/php-request-handler.ts @@ -42,6 +42,8 @@ export type FileNotFoundGetActionCallback = ( relativePath: string ) => FileNotFoundAction; +export type CookieStrategy = 'internal-store' | 'pass-through'; + interface BaseConfiguration { /** * The directory in the PHP filesystem where the server will look @@ -107,7 +109,7 @@ export type PHPRequestHandlerConfiguration = BaseConfiguration & * * Default value is `internal-store`. */ - cookieStrategy?: 'internal-store' | 'pass-through'; + cookieStrategy?: CookieStrategy; }; /** diff --git a/packages/playground/wordpress/src/boot.ts b/packages/playground/wordpress/src/boot.ts index 111a0cd75f..370a81dc0f 100644 --- a/packages/playground/wordpress/src/boot.ts +++ b/packages/playground/wordpress/src/boot.ts @@ -1,4 +1,5 @@ import { + CookieStrategy, FileNotFoundAction, FileNotFoundGetActionCallback, FileTree, @@ -91,6 +92,19 @@ export interface BootOptions { * given request URI. */ getFileNotFoundAction?: FileNotFoundGetActionCallback; + + /** + * - `internal-store`: Persist cookies from reponses in an internal store and + * includen them in following requests. This is a behavior mostly needed when + * using Playground in web. + * + * - `pass-through`: Avoid persisting cookies internally and let them pass + * through the requests and back to the caller from responses. This is the + * common behavior in requests handled by a server. + * + * Default value is `internal-store`. + */ + cookieStrategy?: CookieStrategy; } /** @@ -179,6 +193,7 @@ export async function bootWordPress(options: BootOptions) { rewriteRules: wordPressRewriteRules, getFileNotFoundAction: options.getFileNotFoundAction ?? getFileNotFoundActionForWordPress, + cookieStrategy: options.cookieStrategy, }); const php = await requestHandler.getPrimaryPhp(); From e532d08d8191163f5db31eabf02abbc0b68f2221 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Wed, 11 Sep 2024 17:04:35 +0200 Subject: [PATCH 4/8] Use `pass-through` cookie strategy in Playground CLI --- packages/playground/cli/src/cli.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/playground/cli/src/cli.ts b/packages/playground/cli/src/cli.ts index 85084761fa..825484df8c 100644 --- a/packages/playground/cli/src/cli.ts +++ b/packages/playground/cli/src/cli.ts @@ -311,6 +311,7 @@ async function run() { } }, }, + cookieStrategy: 'pass-through', }); const php = await requestHandler.getPrimaryPhp(); From 493859496797428fe980abce68706bb2eadee0e9 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Wed, 11 Sep 2024 18:14:34 +0200 Subject: [PATCH 5/8] Add mechanism to allow auto-login in Playground CLI --- packages/playground/cli/src/cli.ts | 6 +++++- packages/playground/cli/src/server.ts | 29 +++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/playground/cli/src/cli.ts b/packages/playground/cli/src/cli.ts index 825484df8c..3eecbb1cf2 100644 --- a/packages/playground/cli/src/cli.ts +++ b/packages/playground/cli/src/cli.ts @@ -209,7 +209,6 @@ async function run() { php: args.php as SupportedPHPVersion, wp: args.wp, }, - login: args.login, }; } @@ -349,6 +348,11 @@ async function run() { process.exit(0); } else { logger.log(`WordPress is running on ${absoluteUrl}`); + if (args.login) { + logger.log( + `➜ You can auto-login to the site using the query parameter "playground-auto-login=true"\n➜ Homepage: ${absoluteUrl}/?playground-auto-login=true\n➜ WP-Admin: ${absoluteUrl}/wp-admin/?playground-auto-login=true` + ); + } } }, async handleRequest(request: PHPRequest) { diff --git a/packages/playground/cli/src/server.ts b/packages/playground/cli/src/server.ts index 55e3b99376..8e7a7fcfdb 100644 --- a/packages/playground/cli/src/server.ts +++ b/packages/playground/cli/src/server.ts @@ -25,6 +25,35 @@ export async function startServer(options: ServerOptions) { }); }); + // Middleware to check if auto-login should be executed + app.use(async (req, res, next) => { + if (req.query['playground-auto-login'] === 'true') { + await options.handleRequest({ url: '/wp-login.php' }); + const response = await options.handleRequest({ + url: '/wp-login.php', + method: 'POST', + body: { + log: 'admin', + pwd: 'password', + rememberme: 'forever', + }, + }); + const cookies = response.headers['set-cookie']; + res.setHeader('set-cookie', cookies); + // Remove query parameter to avoid infinite loop + let redirectUrl = req.url.replace( + /&?playground-auto-login=true/, + '' + ); + // If no more query parameters, remove ? from URL + if (Object.keys(req.query).length === 1) { + redirectUrl = redirectUrl.substring(0, redirectUrl.length - 1); + } + return res.redirect(redirectUrl); + } + next(); + }); + app.use('/', async (req, res) => { const phpResponse = await options.handleRequest({ url: req.url, From ef8e39e8df1c8539569413f820a553a3e5a44aa1 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Thu, 12 Sep 2024 12:32:29 +0200 Subject: [PATCH 6/8] Update description of `cookieStrategy` option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Adam Zieliński --- packages/playground/wordpress/src/boot.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/playground/wordpress/src/boot.ts b/packages/playground/wordpress/src/boot.ts index 370a81dc0f..0fd6097219 100644 --- a/packages/playground/wordpress/src/boot.ts +++ b/packages/playground/wordpress/src/boot.ts @@ -95,12 +95,17 @@ export interface BootOptions { /** * - `internal-store`: Persist cookies from reponses in an internal store and - * includen them in following requests. This is a behavior mostly needed when - * using Playground in web. + * includes them in following requests. This behavior is useful in the Playground + * web app because it allows multiple sites to set cookies on the same domain + * without running into conflicts. Each site gets a separate "namespace". The + * downside is that all cookies are global and you cannot have two users + * simultaneously logged in. * - * - `pass-through`: Avoid persisting cookies internally and let them pass - * through the requests and back to the caller from responses. This is the - * common behavior in requests handled by a server. + * - `pass-through`: Typical server behavior. All cookies are passed back to the + * client via a HTTP response. This behavior is useful in when Playground is + * running on the backend and can be requested by multiple browsers. This enables + * each browser to get its own cookies, which means two users may be simultaneously + * logged in to their accounts. * * Default value is `internal-store`. */ From 364e7de10fca4d942a4c587fad6e5dc2cca9dba8 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Thu, 12 Sep 2024 12:31:47 +0200 Subject: [PATCH 7/8] Add unit test to cover not passing cookie strategy --- .../node/src/test/php-request-handler.spec.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/packages/php-wasm/node/src/test/php-request-handler.spec.ts b/packages/php-wasm/node/src/test/php-request-handler.spec.ts index 368fba5d5d..3d372f67a9 100644 --- a/packages/php-wasm/node/src/test/php-request-handler.spec.ts +++ b/packages/php-wasm/node/src/test/php-request-handler.spec.ts @@ -767,12 +767,48 @@ describe('PHPRequestHandler – Loopback call', () => { }); describe('PHPRequestHandler – Cookie strategy', () => { + it('should persist cookies internally when not defining a strategy', async () => { + const handler = new PHPRequestHandler({ + documentRoot: '/', + phpFactory: async () => + new PHP(await loadNodeRuntime(RecommendedPHPVersion)), + maxPhpInstances: 1, + }); + const php = await handler.getPrimaryPhp(); + + php.writeFile( + '/set-cookie.php', + ` { const handler = new PHPRequestHandler({ documentRoot: '/', phpFactory: async () => new PHP(await loadNodeRuntime(RecommendedPHPVersion)), maxPhpInstances: 1, + cookieStrategy: 'internal-store', }); const php = await handler.getPrimaryPhp(); From 3ab3876f9eb07c1ecae4e6c6b1028f907d133f83 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Thu, 12 Sep 2024 12:35:16 +0200 Subject: [PATCH 8/8] Move `cookieStrategy` description to type definition --- .../universal/src/lib/php-request-handler.ts | 27 +++++++++++-------- packages/playground/wordpress/src/boot.ts | 16 ----------- 2 files changed, 16 insertions(+), 27 deletions(-) diff --git a/packages/php-wasm/universal/src/lib/php-request-handler.ts b/packages/php-wasm/universal/src/lib/php-request-handler.ts index 30666f2004..c9d3714c07 100644 --- a/packages/php-wasm/universal/src/lib/php-request-handler.ts +++ b/packages/php-wasm/universal/src/lib/php-request-handler.ts @@ -42,6 +42,22 @@ export type FileNotFoundGetActionCallback = ( relativePath: string ) => FileNotFoundAction; +/** + * - `internal-store`: Persist cookies from reponses in an internal store and + * includes them in following requests. This behavior is useful in the Playground + * web app because it allows multiple sites to set cookies on the same domain + * without running into conflicts. Each site gets a separate "namespace". The + * downside is that all cookies are global and you cannot have two users + * simultaneously logged in. + * + * - `pass-through`: Typical server behavior. All cookies are passed back to the + * client via a HTTP response. This behavior is useful when Playground is running + * on the backend and can be requested by multiple browsers. This enables each + * browser to get its own cookies, which means two users may be simultaneously + * logged in to their accounts. + * + * Default value is `internal-store`. + */ export type CookieStrategy = 'internal-store' | 'pass-through'; interface BaseConfiguration { @@ -98,17 +114,6 @@ export type PHPRequestHandlerConfiguration = BaseConfiguration & maxPhpInstances?: number; } ) & { - /** - * - `internal-store`: Persist cookies from reponses in an internal store and - * includen them in following requests. This is a behavior mostly needed when - * using Playground in web. - * - * - `pass-through`: Avoid persisting cookies internally and let them pass - * through the requests and back to the caller from responses. This is the - * common behavior in requests handled by a server. - * - * Default value is `internal-store`. - */ cookieStrategy?: CookieStrategy; }; diff --git a/packages/playground/wordpress/src/boot.ts b/packages/playground/wordpress/src/boot.ts index 0fd6097219..2ba7d06883 100644 --- a/packages/playground/wordpress/src/boot.ts +++ b/packages/playground/wordpress/src/boot.ts @@ -93,22 +93,6 @@ export interface BootOptions { */ getFileNotFoundAction?: FileNotFoundGetActionCallback; - /** - * - `internal-store`: Persist cookies from reponses in an internal store and - * includes them in following requests. This behavior is useful in the Playground - * web app because it allows multiple sites to set cookies on the same domain - * without running into conflicts. Each site gets a separate "namespace". The - * downside is that all cookies are global and you cannot have two users - * simultaneously logged in. - * - * - `pass-through`: Typical server behavior. All cookies are passed back to the - * client via a HTTP response. This behavior is useful in when Playground is - * running on the backend and can be requested by multiple browsers. This enables - * each browser to get its own cookies, which means two users may be simultaneously - * logged in to their accounts. - * - * Default value is `internal-store`. - */ cookieStrategy?: CookieStrategy; }