Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 65 additions & 3 deletions classes/patreon_login.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,67 @@ public static function clear_user_token_data($user_id)
delete_user_meta($user_id, 'patreon_token_minted');
}

/**
* Generate an OAuth flow nonce for the current user and store it as a transient.
* Returns an array with 'nonce' and 'user_id' to embed in the OAuth state parameter.
* If no user is logged in, returns an empty array.
*/
public static function generate_oauth_flow_nonce()
{
if (!is_user_logged_in()) {
return [];
}

$user_id = get_current_user_id();
$nonce = wp_generate_password(32, false);
$transient_key = 'patreon_oauth_nonce_' . $user_id;

// Store nonce with 10-minute TTL
set_transient($transient_key, $nonce, 10 * MINUTE_IN_SECONDS);

return [
'oauth_nonce' => $nonce,
'oauth_user_id' => $user_id,
];
}

/**
* Verify that the OAuth flow nonce in the state parameter matches the logged-in user.
* Returns true if the nonce is valid and belongs to the current user, false otherwise.
*/
public static function verify_oauth_flow_nonce($state)
{
if (!is_user_logged_in()) {
return false;
}

if (!isset($state['oauth_nonce']) || !isset($state['oauth_user_id'])) {
return false;
}

$user_id = get_current_user_id();

// The state must claim to belong to the currently logged-in user
if ((int) $state['oauth_user_id'] !== $user_id) {
return false;
}

$transient_key = 'patreon_oauth_nonce_' . $user_id;
$stored_nonce = get_transient($transient_key);

if (false === $stored_nonce) {
return false;
}

// Constant-time comparison
$valid = hash_equals($stored_nonce, $state['oauth_nonce']);

// Delete the transient after use (one-time use nonce)
delete_transient($transient_key);

return $valid;
}

public static function updateExistingUser($user_id, $user_response, $tokens)
{
/* update user meta data with patreon data */
Expand Down Expand Up @@ -105,7 +166,7 @@ public static function checkTokenExpiration($user_id = false)
}
}

public static function createOrLogInUserFromPatreon($user_response, $tokens, $redirect = false)
public static function createOrLogInUserFromPatreon($user_response, $tokens, $redirect = false, $state = [])
{
global $wpdb;

Expand All @@ -115,8 +176,9 @@ public static function createOrLogInUserFromPatreon($user_response, $tokens, $re

// Check if user is logged in to wp:

// Logged in user. We just link the user up and be done.
if (is_user_logged_in()) {
// Logged in user. Only link if the OAuth flow nonce confirms this user initiated the flow.
// If verification fails, fall through to the not-logged-in path (lookup/create).
if (is_user_logged_in() && self::verify_oauth_flow_nonce($state)) {
$user = wp_get_current_user();

self::updateExistingUser($user->ID, $user_response, $tokens);
Expand Down
12 changes: 11 additions & 1 deletion classes/patreon_routing.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ public function parse_request(&$wp)
'final_redirect_uri' => $final_redirect,
];

// Embed OAuth flow nonce so the callback can verify which user initiated this flow
$state = array_merge($state, Patreon_Login::generate_oauth_flow_nonce());

// Below filter vars and the following filter allows plugin devs to acquire/filter info about Patron/user + content before going to Patreon flow

$filter_args = [
Expand Down Expand Up @@ -128,6 +131,9 @@ public function parse_request(&$wp)
$state['final_redirect_uri'] = $redirect;
$send_pledge_level = $patreon_level * 100;

// Embed OAuth flow nonce so the callback can verify which user initiated this flow
$state = array_merge($state, Patreon_Login::generate_oauth_flow_nonce());

$flow_link = Patreon_Frontend::MakeUniversalFlowLink($send_pledge_level, $state, $client_id, $post, ['link_interface_item' => $link_interface_item]);

wp_redirect($flow_link);
Expand Down Expand Up @@ -212,6 +218,9 @@ public function parse_request(&$wp)

$state['final_redirect_uri'] = $final_redirect;

// Embed OAuth flow nonce so the callback can verify which user initiated this flow
$state = array_merge($state, Patreon_Login::generate_oauth_flow_nonce());

$send_pledge_level = $patreon_level * 100;

// Below filter vars and the following filter allows plugin devs to acquire/filter info about Patron/user + content before going to Patreon flow
Expand Down Expand Up @@ -586,7 +595,8 @@ private function handle_authorization_flow($wp)
if (apply_filters('ptrn/force_strict_oauth', get_option('patreon-enable-strict-oauth', false))) {
$user = Patreon_Login::updateLoggedInUserForStrictoAuth($user_response, $tokens, $redirect);
} else {
$user = Patreon_Login::createOrLogInUserFromPatreon($user_response, $tokens, $redirect);
$state_for_login = isset($state) ? $state : [];
$user = Patreon_Login::createOrLogInUserFromPatreon($user_response, $tokens, $redirect, $state_for_login);
}

// shouldn't get here
Expand Down
8 changes: 4 additions & 4 deletions classes/patreon_user_profiles.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,15 @@ public function patreon_user_profile_fields($user)
</td>
</tr>
<tr>
<th><label for="user_firstname"><?php _e('Patreon First name'); ?></label></th>
<th><label for="patreon_user_firstname"><?php _e('Patreon First name'); ?></label></th>
<td>
<input type="text" name="user_firstname" id="user_firstname" disabled value="<?php echo esc_attr(get_the_author_meta('user_firstname', $user->ID)); ?>" class="regular-text" /><br />
<input type="text" name="patreon_user_firstname" id="patreon_user_firstname" disabled value="<?php echo esc_attr(get_user_meta($user->ID, 'patreon_user_firstname', true)); ?>" class="regular-text" /><br />
</td>
</tr>
<tr>
<th><label for="user_lastname"><?php _e('Patreon Last name'); ?></label></th>
<th><label for="patreon_user_lastname"><?php _e('Patreon Last name'); ?></label></th>
<td>
<input type="text" name="user_lastname" id="user_lastname" disabled value="<?php echo esc_attr(get_the_author_meta('user_lastname', $user->ID)); ?>" class="regular-text" /><br />
<input type="text" name="patreon_user_lastname" id="patreon_user_lastname" disabled value="<?php echo esc_attr(get_user_meta($user->ID, 'patreon_user_lastname', true)); ?>" class="regular-text" /><br />
</td>
</tr>
</table>
Expand Down
4 changes: 2 additions & 2 deletions classes/patreon_wordpress.php
Original file line number Diff line number Diff line change
Expand Up @@ -319,8 +319,8 @@ public static function updatePatreonUser()
}

update_user_meta($user->ID, 'patreon_created', $patreon_created);
update_user_meta($user->ID, 'user_firstname', $user_response['data']['attributes']['first_name']);
update_user_meta($user->ID, 'user_lastname', $user_response['data']['attributes']['last_name']);
update_user_meta($user->ID, 'patreon_user_firstname', $user_response['data']['attributes']['first_name']);
update_user_meta($user->ID, 'patreon_user_lastname', $user_response['data']['attributes']['last_name']);
}
}

Expand Down