-
Notifications
You must be signed in to change notification settings - Fork 3
feat: AJAX uses token authentication #181
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Conversation
a051ea4 to
682e0df
Compare
Include authorization header in AJAX requets, as we do not have cookies to send in the mobile app environment.
If we configure AJAX before loading the library, the configuration is overridden.
This global is often used by WordPress Admin page scripts.
Useful when needing to allow CORS for specific domains.
686ebf7 to
bbaf9c4
Compare
|
Claude finished @dcalhoun's task in 4m 47s —— View job Code Review Complete ✅I've completed a comprehensive review of PR #181. Overall, this is a well-implemented and thoroughly tested feature that successfully replaces the VideoPress-specific bridge with a general AJAX authentication solution. The code quality is high, security is properly handled, and the documentation is excellent. SummaryThis PR introduces token-based authentication for AJAX requests via authorization headers, replacing the previous VideoPress-specific bridge ( Key Changes:
Detailed Findings1. Code Quality ⭐⭐⭐⭐⭐Strengths:
Minor Observations:
2. Security ⭐⭐⭐⭐⭐Strengths:
Observations:
3. Performance ⭐⭐⭐⭐⭐Strengths:
Observations:
4. Testing ⭐⭐⭐⭐⭐Strengths:
Observations:
5. Documentation ⭐⭐⭐⭐⭐Strengths:
Observations:
Issues FoundCritical IssuesNone ❌ High Priority IssuesNone ❌ Medium Priority Issues1. Potential Race Condition with
|
| window.jQuery?.ajaxSetup( { | ||
| headers: { | ||
| Authorization: authHeader, | ||
| }, | ||
| } ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor observation: This sets the authorization header globally for all jQuery AJAX requests. Combined with the per-request header setting in the wp.ajax.send/wp.ajax.post wrappers below, this means wp.ajax requests will have the header set twice.
This is harmless and provides defense-in-depth, but worth noting. The redundancy ensures the auth header is present even if one mechanism fails.
| val enableNetworkLogging: Boolean = false, | ||
| val assetLoaderDomain: String? = null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great addition! The assetLoaderDomain configuration properly addresses AJAX CORS requirements for Android. The nullable type with null default is correct since it falls back to DEFAULT_ASSET_DOMAIN if not specified (GutenbergView.kt:262).
The documentation in docs/integration.md clearly explains that this must be set to a domain allowed by the WordPress site's CORS policy for AJAX to work on Android.
| describe( 'Edge cases', () => { | ||
| it( 'should handle missing jQuery gracefully', () => { | ||
| bridge.getGBKit.mockReturnValue( { | ||
| siteURL: 'https://example.com', | ||
| authHeader: 'Bearer no-jquery', | ||
| } ); | ||
|
|
||
| delete global.window.jQuery; | ||
|
|
||
| expect( () => configureAjax() ).not.toThrow(); | ||
| expect( logger.debug ).toHaveBeenCalledWith( | ||
| 'AJAX URL configured' | ||
| ); | ||
| expect( logger.debug ).toHaveBeenCalledWith( | ||
| 'AJAX auth configured' | ||
| ); | ||
| } ); | ||
|
|
||
| it( 'should handle undefined jQuery', () => { | ||
| bridge.getGBKit.mockReturnValue( { | ||
| siteURL: 'https://example.com', | ||
| authHeader: 'Bearer undefined-jquery', | ||
| } ); | ||
|
|
||
| global.window.jQuery = undefined; | ||
|
|
||
| expect( () => configureAjax() ).not.toThrow(); | ||
| expect( logger.debug ).toHaveBeenCalledWith( | ||
| 'AJAX URL configured' | ||
| ); | ||
| expect( logger.debug ).toHaveBeenCalledWith( | ||
| 'AJAX auth configured' | ||
| ); | ||
| } ); | ||
|
|
||
| it( 'should handle missing wp.ajax.send method', () => { | ||
| bridge.getGBKit.mockReturnValue( { | ||
| siteURL: 'https://example.com', | ||
| authHeader: 'Bearer no-send', | ||
| } ); | ||
|
|
||
| global.window.wp = { | ||
| ajax: { | ||
| post: originalWpAjaxPost, | ||
| settings: {}, | ||
| }, | ||
| }; | ||
|
|
||
| expect( () => configureAjax() ).not.toThrow(); | ||
|
|
||
| // Should still wrap post | ||
| expect( global.window.wp.ajax.post ).not.toBe( originalWpAjaxPost ); | ||
| } ); | ||
|
|
||
| it( 'should handle missing wp.ajax.post method', () => { | ||
| bridge.getGBKit.mockReturnValue( { | ||
| siteURL: 'https://example.com', | ||
| authHeader: 'Bearer no-post', | ||
| } ); | ||
|
|
||
| global.window.wp = { | ||
| ajax: { | ||
| send: originalWpAjaxSend, | ||
| settings: {}, | ||
| }, | ||
| }; | ||
|
|
||
| expect( () => configureAjax() ).not.toThrow(); | ||
|
|
||
| // Should still wrap send | ||
| expect( global.window.wp.ajax.send ).not.toBe( originalWpAjaxSend ); | ||
| } ); | ||
|
|
||
| it( 'should handle missing wp.ajax entirely', () => { | ||
| bridge.getGBKit.mockReturnValue( { | ||
| siteURL: 'https://example.com', | ||
| authHeader: 'Bearer no-ajax', | ||
| } ); | ||
|
|
||
| global.window.wp = {}; | ||
|
|
||
| expect( () => configureAjax() ).not.toThrow(); | ||
|
|
||
| // Should create ajax object | ||
| expect( global.window.wp.ajax ).toBeDefined(); | ||
| expect( global.window.wp.ajax.settings ).toBeDefined(); | ||
| } ); | ||
|
|
||
| it( 'should work with window.wp already partially initialized', () => { | ||
| bridge.getGBKit.mockReturnValue( { | ||
| siteURL: 'https://example.com', | ||
| authHeader: null, | ||
| } ); | ||
|
|
||
| // Pre-existing wp object with other properties | ||
| global.window.wp = { | ||
| data: { someData: 'test' }, | ||
| }; | ||
|
|
||
| configureAjax(); | ||
|
|
||
| // Should preserve existing properties | ||
| expect( global.window.wp.data ).toEqual( { someData: 'test' } ); | ||
|
|
||
| // Should add ajax properties | ||
| expect( global.window.wp.ajax ).toBeDefined(); | ||
| expect( global.window.wp.ajax.settings.url ).toBe( | ||
| 'https://example.com/wp-admin/admin-ajax.php' | ||
| ); | ||
| } ); | ||
|
|
||
| it( 'should work when wp.ajax is partially initialized', () => { | ||
| bridge.getGBKit.mockReturnValue( { | ||
| siteURL: 'https://example.com', | ||
| authHeader: null, | ||
| } ); | ||
|
|
||
| // Pre-existing wp.ajax object without settings | ||
| global.window.wp = { | ||
| ajax: { | ||
| someMethod: vi.fn(), | ||
| }, | ||
| }; | ||
|
|
||
| configureAjax(); | ||
|
|
||
| // Should preserve existing methods | ||
| expect( global.window.wp.ajax.someMethod ).toBeDefined(); | ||
|
|
||
| // Should add settings | ||
| expect( global.window.wp.ajax.settings ).toBeDefined(); | ||
| expect( global.window.wp.ajax.settings.url ).toBe( | ||
| 'https://example.com/wp-admin/admin-ajax.php' | ||
| ); | ||
| } ); | ||
| } ); | ||
| } ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent edge case testing! 👏
This test suite demonstrates exceptional attention to detail with comprehensive coverage of edge cases:
- Missing jQuery gracefully handled
- Undefined jQuery scenarios
- Missing
wp.ajaxmethods don't cause crashes - Partial initialization states
- Pre-existing
window.wpproperties preserved
The 491 lines of tests provide strong confidence in the robustness of the AJAX configuration. Well done!
|
|
||
| // Set up asset loader domain | ||
| assetDomain = configuration.assetLoaderDomain ?: DEFAULT_ASSET_DOMAIN | ||
|
|
||
| // Initialize asset loader with configured domain | ||
| assetLoader = WebViewAssetLoader.Builder() | ||
| .setDomain(assetDomain) | ||
| .addPathHandler("/assets/", AssetsPathHandler(this.context)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great implementation! The configurable assetLoaderDomain is properly implemented here:
- Falls back to
DEFAULT_ASSET_DOMAINif not configured - Used to initialize the
WebViewAssetLoaderwith the correct domain - Properly constructed into the
editorUrl
This enables Android apps to configure a domain that their WordPress site allows in CORS policies, which is essential for AJAX functionality to work.
Address PR feedback about potential race condition. The code now checks if `window.wp.ajax.send` and `window.wp.ajax.post` are functions before wrapping them. This prevents TypeError when calling the wrapped function if the original method was undefined during configuration. Update tests to verify that missing methods remain undefined rather than being wrapped with an undefined reference. Co-authored-by: Claude <noreply@anthropic.com>
Related:
@wordpress/api-fetchutility Automattic/jetpack#45254What?
Authenticate AJAX requests with application passwords send via an authorization
header.
Why?
The GutenbergKit editor does not have authorization cookies, so we must rely
upon a different authorization mechanism.
Ref CMM-713. Close CMM-768.
How?
Set the
Authorizationheader viajQuery.ajaxSetupand by overloading thewindow.wp.ajaxutilities.Testing Instructions
Tip
Test using prototype builds from the sibling PRs:
Accessibility Testing Instructions
N/A, no navigation changes.
Screenshots or screencast
N/A, no visual changes.