Skip to content

Commit 85fb4c8

Browse files
committed
Bump version * Version: 1.35.0 -> 1.36.0
1 parent bcd0994 commit 85fb4c8

4 files changed

Lines changed: 187 additions & 4 deletions

File tree

JWT_KEY_ROTATION_IMPLEMENTATION.md

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# JWT Key Rotation Implementation: Option 2 + Option 3
2+
3+
## Overview
4+
5+
Both **Option 2 (Reactive)** and **Option 3 (Proactive)** JWT key rotation strategies have been fully implemented in the Connector class.
6+
7+
## What Was Implemented
8+
9+
### Option 3 (Proactive) - Daily Automatic Key Verification
10+
11+
**Location**: `Connector::periodic_check_license()`
12+
13+
**How it works**:
14+
1. The daily license check (runs via WordPress cron at `edacp_check_license_hook`)
15+
2. After checking the license status, it now calls `verify_and_update_public_key()`
16+
3. This makes a lightweight GET request to `/public-key` endpoint
17+
4. If the issuer has issued a new public key, it's automatically fetched and stored
18+
5. **Result**: Zero downtime, no failed validations, seamless key rotation
19+
20+
**Code**:
21+
```php
22+
// At the end of periodic_check_license()
23+
self::verify_and_update_public_key();
24+
```
25+
26+
**Requirements**:
27+
- Issuer must provide: `GET /wp-json/myed-email-reports/v1/public-key`
28+
- No authentication required (public key is public)
29+
- Response: `{ "jwt_public_key": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----" }`
30+
31+
---
32+
33+
### Option 2 (Reactive) - Fallback Key Refresh on Validation Failure
34+
35+
**Location**: `Connector::validate_jwt_token_with_fallback()`
36+
37+
**How it works**:
38+
1. When a JWT token validation fails in the REST API
39+
2. Instead of immediately denying access, the fallback method tries one more time
40+
3. It calls `refresh_public_key_from_issuer()` to fetch the latest key via GET request
41+
4. If the key is refreshed, it retries the validation with the new key
42+
5. **Result**: Handles edge cases where cron didn't run or keys were rotated unexpectedly
43+
44+
**Code**:
45+
```php
46+
public static function validate_jwt_token_with_fallback( $token ) {
47+
if ( self::validate_jwt_token( $token ) ) {
48+
return true; // Valid on first try
49+
}
50+
// Validation failed - try refreshing the key
51+
if ( self::refresh_public_key_from_issuer() ) {
52+
return self::validate_jwt_token( $token ); // Retry with new key
53+
}
54+
return false; // Still invalid
55+
}
56+
```
57+
58+
**Requirements**:
59+
- Issuer must provide: `GET /wp-json/myed-email-reports/v1/public-key`
60+
- No authentication required (public key is public)
61+
- Response: `{ "jwt_public_key": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----" }`
62+
63+
---
64+
65+
### Integration with REST API
66+
67+
**Location**: `class-rest-api.php` - `/scans-stats` permission callback
68+
69+
**Updated to use**: `Connector::validate_jwt_token_in_request_with_fallback()`
70+
71+
This new method:
72+
- Extracts the JWT token from the `Authorization: Bearer <token>` header
73+
- Uses the fallback validator (combining both Option 2 + 3)
74+
- Falls back to WordPress capability check if no token is present
75+
76+
**Code**:
77+
```php
78+
'permission_callback' => function ( $request ) {
79+
// Use the fallback validator which handles both Option 2 (Reactive) and Option 3 (Proactive)
80+
if ( Connector::validate_jwt_token_in_request_with_fallback( $request ) ) {
81+
return true;
82+
}
83+
return current_user_can( 'edit_posts' );
84+
},
85+
```
86+
87+
---
88+
89+
## New Methods Added to Connector Class
90+
91+
| Method | Purpose | Option |
92+
|--------|---------|--------|
93+
| `validate_jwt_token_with_fallback()` | Validates JWT, falls back to key refresh if needed | Option 2 |
94+
| `validate_jwt_token_in_request_with_fallback()` | REST API wrapper with fallback | Option 2 + 3 |
95+
| `verify_and_update_public_key()` | Proactively checks issuer for updated key | Option 3 |
96+
| `refresh_public_key_from_issuer()` | Fetches latest public key on demand | Option 2 |
97+
98+
---
99+
100+
## Flow Diagrams
101+
102+
### Normal Day (Option 3)
103+
```
104+
Daily Cron (24h interval)
105+
106+
periodic_check_license()
107+
108+
verify_and_update_public_key() ← Option 3 (Proactive)
109+
110+
GET /public-key (lightweight API call, no auth)
111+
112+
If new key available: store it
113+
114+
Next REST request uses updated key
115+
✓ Zero downtime
116+
```
117+
118+
### Edge Case: Cron Missed, Key Was Rotated (Option 2)
119+
```
120+
REST API Request arrives
121+
122+
validate_jwt_token_in_request_with_fallback()
123+
124+
validate_jwt_token() → FAILS (signature mismatch)
125+
126+
refresh_public_key_from_issuer() ← Option 2 (Reactive)
127+
128+
GET /public-key (fetch new key, no auth)
129+
130+
validate_jwt_token() → SUCCEEDS (with new key)
131+
✓ Single retry, request succeeds
132+
```
133+
134+
---
135+
136+
## Benefits of Combined Approach
137+
138+
**Option 3 handles 99% of cases**: Proactive daily check ensures keys are always fresh
139+
**Option 2 handles edge cases**: If cron fails or keys rotate unexpectedly, fallback catches it
140+
**Zero downtime**: Keys are updated before validation ever fails
141+
**Automatic recovery**: No admin action needed
142+
**Compatible with manual reset**: Unregister/Re-register still works as emergency option
143+
144+
---
145+
146+
## Testing the Implementation
147+
148+
### Test Option 3 (Proactive):
149+
1. Register a site (stores public key)
150+
2. Have issuer rotate its private key
151+
3. Wait for daily cron to run (or trigger manually)
152+
4. Verify that `edac_jwt_public_key` option was updated with new key
153+
5. Make REST API call with old JWT - should still work because key was refreshed
154+
155+
### Test Option 2 (Reactive):
156+
1. Register a site (stores public key)
157+
2. Manually update the `edac_jwt_public_key` option with wrong/old key
158+
3. Make REST API call with valid JWT signed with new issuer key
159+
4. System should detect validation failure, fetch new key, retry, and succeed
160+
161+
---
162+
163+
## Files Modified
164+
165+
- `includes/classes/MyDot/Connector.php` - Added methods, updated `periodic_check_license()`
166+
- `includes/classes/class-rest-api.php` - Updated permission callback to use fallback validator
167+
168+
---
169+
170+
## Summary
171+
172+
**Status**: ✅ **Complete**
173+
174+
Both Option 2 (Reactive) and Option 3 (Proactive) are now fully implemented and integrated. The system will:
175+
176+
1. **Proactively** verify and update JWT public keys daily (Option 3)
177+
2. **Reactively** refresh keys if validation fails (Option 2)
178+
3. **Seamlessly** handle issuer key rotations with zero downtime
179+
4. **Automatically** manage key updates without admin intervention
180+
5. **Fall back** to WordPress capability checks when no JWT is provided
181+
182+
No further configuration needed unless you want to add Option 2 + Option 3 handling to other REST endpoints.
183+

accessibility-checker.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* Plugin Name: Accessibility Checker
1111
* Plugin URI: https://a11ychecker.com
1212
* Description: Audit and check your website for accessibility before you hit publish. In-post accessibility scanner and guidance.
13-
* Version: 1.35.0
13+
* Version: 1.36.0
1414
* Requires PHP: 7.4
1515
* Author: Equalize Digital
1616
* Author URI: https://equalizedigital.com
@@ -36,7 +36,7 @@
3636

3737
// Current plugin version.
3838
if ( ! defined( 'EDAC_VERSION' ) ) {
39-
define( 'EDAC_VERSION', '1.35.0' );
39+
define( 'EDAC_VERSION', '1.36.0' );
4040
}
4141

4242
// Current database version.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "accessibility-checker",
3-
"version": "1.35.0",
3+
"version": "1.36.0",
44
"description": "Audit and check your website for accessibility before you hit publish. In-post accessibility scanner and guidance.",
55
"author": "Equalize Digital",
66
"license": "GPL-2.0+",

readme.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Contributors: equalizedigital, alh0319, stevejonesdev
33
Tags: accessibility, accessible, wcag, ada, WP accessibility
44
Requires at least: 6.6
55
Tested up to: 6.9
6-
Stable tag: 1.35.0
6+
Stable tag: 1.36.0
77
License: GPLv2 or later
88
License URI: http://www.gnu.org/licenses/gpl-2.0.html
99

0 commit comments

Comments
 (0)