✨feature: Initial functional push
This commit is contained in:
76
includes/class-auth.php
Normal file
76
includes/class-auth.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace SiteSync;
|
||||
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles authentication for SiteSync REST requests using shared key and HMAC.
|
||||
*/
|
||||
class Auth {
|
||||
/**
|
||||
* Verify an incoming REST request using shared key + HMAC.
|
||||
*
|
||||
* @param WP_REST_Request $request The REST request object.
|
||||
* @param Settings $settings The SiteSync settings instance.
|
||||
*/
|
||||
public static function verify( WP_REST_Request $request, Settings $settings ) {
|
||||
$providedKey = $request->get_header( 'X-Site-Sync-Key' );
|
||||
$signature = $request->get_header( 'X-Site-Sync-Signature' );
|
||||
$timestamp = $request->get_header( 'X-Site-Sync-Timestamp' );
|
||||
|
||||
if ( ! $providedKey || ! $signature || ! $timestamp ) {
|
||||
return new WP_Error( 'site_sync_missing_headers', __( 'Missing authentication headers.', 'site-sync' ), array( 'status' => 401 ) );
|
||||
}
|
||||
|
||||
$peerKey = $settings->get( 'peer_site_key' );
|
||||
if ( ! empty( $peerKey ) && ! hash_equals( $peerKey, $providedKey ) ) {
|
||||
return new WP_Error( 'site_sync_invalid_peer', __( 'Peer key does not match.', 'site-sync' ), array( 'status' => 401 ) );
|
||||
}
|
||||
|
||||
$sharedSecret = $settings->get( 'shared_key' );
|
||||
if ( empty( $sharedSecret ) ) {
|
||||
return new WP_Error( 'site_sync_no_secret', __( 'Shared secret is not configured.', 'site-sync' ), array( 'status' => 401 ) );
|
||||
}
|
||||
|
||||
$body = $request->get_body() ?? '';
|
||||
$expected = self::build_signature( $providedKey, $timestamp, $body, $sharedSecret );
|
||||
|
||||
if ( ! hash_equals( $expected, $signature ) ) {
|
||||
return new WP_Error( 'site_sync_bad_signature', __( 'Signature verification failed.', 'site-sync' ), array( 'status' => 401 ) );
|
||||
}
|
||||
|
||||
$ts = is_numeric( $timestamp ) ? (int) $timestamp : 0;
|
||||
if ( $ts > 0 && abs( time() - $ts ) > 300 ) {
|
||||
return new WP_Error( 'site_sync_stale', __( 'Request timestamp is outside the allowed window.', 'site-sync' ), array( 'status' => 401 ) );
|
||||
}
|
||||
|
||||
$cacheKey = 'site_sync_seen_' . md5( $providedKey . $signature . $timestamp );
|
||||
if ( false !== get_transient( $cacheKey ) ) {
|
||||
return new WP_Error( 'site_sync_replay', __( 'Replay detected.', 'site-sync' ), array( 'status' => 401 ) );
|
||||
}
|
||||
set_transient( $cacheKey, 1, 5 * MINUTE_IN_SECONDS );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an HMAC signature for the given key, timestamp, and body using the shared secret.
|
||||
*
|
||||
* @param string $key The peer site key.
|
||||
* @param string $timestamp The request timestamp.
|
||||
* @param string $body The request body.
|
||||
* @param string $secret The shared secret key.
|
||||
* @return string The generated HMAC signature.
|
||||
*/
|
||||
public static function build_signature( string $key, string $timestamp, string $body, string $secret ): string {
|
||||
$canonical = implode( "\n", array( $key, $timestamp, $body ) );
|
||||
|
||||
return hash_hmac( 'sha256', $canonical, $secret );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user