fictioneer/includes/functions/_module-oauth.php
2025-01-17 13:05:40 +01:00

1077 lines
31 KiB
PHP

<?php
// =============================================================================
// CONSTANTS
// =============================================================================
define(
'FCN_OAUTH2_API_ENDPOINTS',
array(
'discord' => array(
'login' => 'https://discord.com/api/oauth2/authorize',
'token' => 'https://discord.com/api/oauth2/token',
'user' => 'https://discord.com/api/users/@me',
'revoke' => 'https://discord.com/api/oauth2/token/revoke',
'scope' => 'identify email'
),
'twitch' => array(
'login' => 'https://id.twitch.tv/oauth2/authorize',
'token' => 'https://id.twitch.tv/oauth2/token',
'user' => 'https://api.twitch.tv/helix/users',
'revoke' => 'https://id.twitch.tv/oauth2/revoke',
'scope' => 'user:read:email'
),
'google' => array(
'login' => 'https://accounts.google.com/o/oauth2/auth',
'token' => 'https://oauth2.googleapis.com/token',
'user' => 'https://www.googleapis.com/oauth2/v1/userinfo',
'revoke' => 'https://oauth2.googleapis.com/revoke',
'scope' => 'openid https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile'
),
'patreon' => array(
'login' => 'https://www.patreon.com/oauth2/authorize',
'token' => 'https://www.patreon.com/api/oauth2/token',
'user' => 'https://www.patreon.com/api/oauth2/v2/identity',
'revoke' => '',
'scope' => urlencode( 'identity identity[email]' )
),
'subscribestar' => array(
'login' => 'https://www.subscribestar.com/oauth2/authorize',
'token' => 'https://www.subscribestar.com/oauth2/token',
'api' => 'https://www.subscribestar.com/api/graphql/v1',
'revoke' => '',
'scope' => 'subscriber.read+subscriber.payments.read+user.read+user.email.read'
)
)
);
// =============================================================================
// PROCESS
// =============================================================================
/**
* Handle OAuth 2.0 requests to get campaign tiers
*
* @since 5.15.0
* @since 5.19.0 - Refactored
*/
function fictioneer_oauth2_get_campaign_tiers() {
// Abort if not on the /oauth2 route...
if ( is_null( get_query_var( FICTIONEER_OAUTH_ENDPOINT, null ) ) ) {
return;
}
// Setup
$cookie = fictioneer_oauth2_get_cookie();
$state = sanitize_key( $_GET['state'] ?? '' );
$code = sanitize_text_field( $_GET['code'] ?? '' ); // Do not use sanitize_key()
// Validate!
if (
! current_user_can( 'manage_options' ) ||
( $cookie['action'] ?? 0 ) !== 'fictioneer_connection_get_patreon_tiers' ||
( $cookie['state'] ?? 0 ) !== $state
) {
return;
}
// Delete cookie
fictioneer_oauth2_delete_cookie();
// Get access token
$token_response = fictioneer_oauth2_get_token(
FCN_OAUTH2_API_ENDPOINTS['patreon']['token'],
array(
'grant_type' => 'authorization_code',
'client_id' => get_option( 'fictioneer_patreon_client_id' ),
'client_secret' => get_option( 'fictioneer_patreon_client_secret' ),
'redirect_uri' => get_site_url( null, FICTIONEER_OAUTH_ENDPOINT ),
'code' => $code,
'scope' => urlencode( 'campaigns' )
)
);
// Token successfully retrieved?
if ( is_wp_error( $token_response ) ) {
fictioneer_oauth_die( $token_response->get_error_message() );
}
// Extract token
$access_token = isset( $token_response->access_token ) ? $token_response->access_token : null;
if ( ! $access_token ) {
fictioneer_oauth2_terminate(
admin_url( 'admin.php?page=fictioneer_connections' ),
array( 'failure' => 'fictioneer-patreon-token-missing' )
);
}
// Build params
$params = '?fields' . urlencode( '[campaign]' ) . '=created_at';
$params .= '&fields' . urlencode( '[tier]' ) . '=title,amount_cents,published,description';
$params .= '&include=tiers';
// Retrieve campaign data from Patreon
$campaign_response = wp_remote_get(
'https://www.patreon.com/api/oauth2/v2/campaigns' . $params,
array(
'headers' => array(
'Authorization' => 'Bearer ' . $access_token
)
)
);
// Request successful?
if ( is_wp_error( $campaign_response ) ) {
fictioneer_oauth_die( $campaign_response->get_error_message() );
} else {
$body = json_decode( wp_remote_retrieve_body( $campaign_response ) );
}
// Data successfully retrieved?
if ( empty( $body ) || json_last_error() !== JSON_ERROR_NONE ) {
fictioneer_oauth_die( wp_remote_retrieve_body( $campaign_response ) );
}
// Data?
if ( ! isset( $body->data ) ) {
fictioneer_oauth_die( 'Data node not found.' );
}
// Includes?
if ( ! isset( $body->included ) ) {
fictioneer_oauth_die( 'Includes node not found.' );
}
// Extract tiers
$tiers = [];
foreach ( $body->included as $item ) {
if ( $item->type !== 'tier' || ! isset( $item->attributes ) ) {
continue;
}
$tiers[ $item->id ] = array(
'id' => absint( $item->id ),
'title' => sanitize_text_field( $item->attributes->title ?? '' ),
'description' => wp_kses_post( $item->attributes->description ?? '' ),
'amount_cents' => absint( $item->attributes->amount_cents ?? 0 ),
'published' => filter_var( $item->attributes->published ?? 0, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ),
);
}
// Save as autoload option
update_option( 'fictioneer_connection_patreon_tiers', $tiers );
// Finish
wp_safe_redirect(
add_query_arg(
array( 'success' => 'fictioneer-patreon-tiers-pulled' ),
admin_url( 'admin.php?page=fictioneer_connections' )
)
);
exit;
}
add_action( 'template_redirect', 'fictioneer_oauth2_get_campaign_tiers', 5 );
/**
* Process any OAuth 2.0 request
*
* @since 5.19.0
* @link https://dev.twitch.tv/docs/authentication
* @link https://discord.com/developers/docs/topics/oauth2
* @link https://docs.patreon.com/#step-1-registering-your-client
* @link https://developers.google.com/identity/protocols/oauth2
*/
function fictioneer_oauth2_process() {
// Abort if not on the /oauth2 route...
if ( is_null( get_query_var( FICTIONEER_OAUTH_ENDPOINT, null ) ) ) {
return;
}
// Setup
$cookie = fictioneer_oauth2_get_cookie();
$action = sanitize_key( $_GET['action'] ?? '' );
$state = sanitize_key( $_GET['state'] ?? '' );
$code = sanitize_text_field( $_GET['code'] ?? '' ); // Do not use sanitize_key()
$channel = sanitize_key( $_GET['channel'] ?? '' );
$merge = ( $_GET['merge'] ?? $cookie['merge'] ?? 0 ) ? 1 : 0;
$anchor = sanitize_key( $_GET['anchor'] ?? $cookie['anchor'] ?? '' );
$return_url = wp_validate_redirect( $_GET['return_url'] ?? $cookie['return_url'] ?? home_url(), home_url() );
$result = '';
// Verify!
if ( ! $state && ! wp_verify_nonce( $_GET['oauth_nonce'] ?? '', 'authenticate' ) ) {
fictioneer_oauth2_terminate(
$return_url,
array( 'failure' => 'oauth_invalid_nonce' ),
$anchor
);
}
// Merge?
if ( ( ! is_user_logged_in() && $merge ) || ( is_user_logged_in() && ! $merge ) ) {
fictioneer_oauth2_terminate(
$return_url,
array( 'failure' => 'oauth_invalid_merge_request' ),
$anchor
);
}
// Login: Get code!
if ( $action === 'login' && ! $state ) {
fictioneer_oauth2_get_code(
array(
'channel' => $channel,
'anchor' => $anchor,
'return_url' => $return_url,
'merge' => $merge,
'merge_id' => get_current_user_id()
)
);
}
// Cookie?
if ( ! $cookie || ! ( $cookie['state'] ?? 0 ) || ! ( $cookie['channel'] ?? 0 ) || ! ( $cookie['return_url'] ?? 0 ) ) {
fictioneer_oauth2_terminate(
$cookie['return_url'],
array( 'failure' => 'oauth_invalid_cookie' ),
$anchor
);
}
// State?
if ( $cookie['state'] !== $state ) {
fictioneer_oauth2_terminate(
$cookie['return_url'],
array( 'failure' => 'oauth_invalid_state' ),
$anchor
);
}
// Code?
if ( ! $code ) {
fictioneer_oauth2_terminate(
$cookie['return_url'],
array( 'failure' => 'oauth_invalid_code' ),
$anchor
);
}
// Code: Get access token!
$token_response = fictioneer_oauth2_get_token(
FCN_OAUTH2_API_ENDPOINTS[ $cookie['channel'] ]['token'],
array(
'grant_type' => 'authorization_code',
'client_id' => get_option( "fictioneer_{$cookie['channel']}_client_id" ),
'client_secret' => get_option( "fictioneer_{$cookie['channel']}_client_secret" ),
'redirect_uri' => get_site_url( null, FICTIONEER_OAUTH_ENDPOINT ),
'code' => $code,
'scope' => FCN_OAUTH2_API_ENDPOINTS[ $cookie['channel'] ]['scope']
)
);
// Error?
if ( is_wp_error( $token_response ) ) {
fictioneer_oauth2_terminate(
$cookie['return_url'],
array( 'failure' => $token_response->get_error_code() ),
$anchor
);
}
// Token?
$access_token = isset( $token_response->access_token ) ? $token_response->access_token : null;
if ( ! $access_token ) {
fictioneer_oauth2_terminate(
$cookie['return_url'],
array( 'failure' => 'oauth_token_missing' ),
$anchor
);
}
// Delegate to respective channel
switch ( $cookie['channel'] ) {
case 'discord':
$result = fictioneer_oauth2_discord( $token_response, $cookie );
break;
case 'patreon':
$result = fictioneer_oauth2_patreon( $token_response, $cookie );
break;
case 'google':
$result = fictioneer_oauth2_google( $token_response, $cookie );
break;
case 'twitch':
$result = fictioneer_oauth2_twitch( $token_response, $cookie );
break;
// case 'subscribestar':
// $result = fictioneer_oauth2_subscribestar( $token_response, $cookie );
// break;
default:
$result = 'oauth_invalid_channel';
}
// Error: Any
if ( ! in_array( $result, ['merged_user', 'new_user', 'known_user'] ) ) {
$return_url = add_query_arg( 'failure', $result, $return_url );
}
// Success: Merged
if ( $result === 'merged_user' ) {
$return_url = add_query_arg( 'success', 'oauth_merged_' . $cookie['channel'], $return_url );
}
// Success: New
if ( $result === 'new_user' ) {
$return_url = add_query_arg( 'success', 'oauth_new', $return_url );
}
// Success: Known
if ( $result === 'known_user' ) {
$return_url = add_query_arg( 'success', 'oauth_known', $return_url );
}
// Finish
fictioneer_oauth2_delete_cookie();
wp_safe_redirect( $return_url . ( $anchor ? '#' . $anchor : '' ) );
exit;
}
add_action( 'template_redirect', 'fictioneer_oauth2_process' );
/**
* Log in or register user
*
* @since 5.19.0
*
* @param array $user_data Array of user data.
* @param array $cookie The decrypted cookie data.
*/
function fictioneer_oauth2_make_user( $user_data, $cookie ) {
// Setup
$channel = $user_data['channel'];
$meta_key = "fictioneer_{$channel}_id_hash";
$channel_id = 'fcn_' . hash( 'sha256', sanitize_key( $user_data['uid'] ) );
$username = sanitize_user( $user_data['username'] );
$nickname = sanitize_user( $user_data['nickname'] );
$email = sanitize_email( $user_data['email'] );
$avatar = fictioneer_url_exists( $user_data['avatar'] ) ? $user_data['avatar'] : null;
$merge_id = absint( $cookie['merge_id'] ?? 0 );
$new = false;
$merged = false;
// Randomize username?
if ( get_option( 'fictioneer_randomize_oauth_usernames' ) ) {
$username = fictioneer_get_random_username();
$nickname = $username;
}
// Look for existing user...
$wp_user = get_users( array( 'meta_key' => $meta_key, 'meta_value' => $channel_id, 'number' => 1 ) );
$wp_user = reset( $wp_user );
// Check for faulty merge request
if ( ! is_user_logged_in() && $cookie['merge'] ) {
return 'oauth_invalid_merge_request';
}
if ( is_user_logged_in() && ! $cookie['merge'] ) {
return 'oauth_invalid_merge_request';
}
// If a valid merge has been requested...
if ( is_user_logged_in() && ! $wp_user && $cookie['merge'] ) {
if ( ! $wp_user && get_current_user_id() === $merge_id ) {
$wp_user = get_user_by( 'id', $merge_id );
$merged = true;
}
// Cannot merge with non-existent user...
if ( is_user_logged_in() && ! $wp_user ) {
return 'oauth_invalid_merge_user';
}
}
// Cannot merged if already linked
if ( is_user_logged_in() && $wp_user && $merge_id !== $wp_user->ID ) {
return 'oauth_already_linked';
}
// If still no user has been found, create a new one...
if ( ! is_a( $wp_user, 'WP_User' ) ) {
// Find free username
if ( username_exists( $username ) ) {
$alt_username = $username;
$discriminator = 1;
while ( username_exists( $alt_username ) ) {
$alt_username = $username . $discriminator;
$discriminator++;
}
$username = $alt_username;
}
// Create user
$wp_user_id = wp_create_user( $username, wp_generate_password( 32, true, true ), $email );
// Failure or success?
if ( is_wp_error( $wp_user_id ) ) {
$error_code = $wp_user_id->get_error_code();
return $error_code === 'existing_user_email' ? 'oauth_email_taken' : $error_code;
} else {
$wp_user = get_user_by( 'id', $wp_user_id );
$new = true;
}
}
// Update user data
if ( is_a( $wp_user, 'WP_User' ) ) {
// Current avatar
$current_avatar = $wp_user->fictioneer_external_avatar_url ?: '';
// Set nickname and display name
if ( $new ) {
fictioneer_update_user_meta( $wp_user->ID, 'nickname', $nickname );
wp_update_user( array( 'ID' => $wp_user->ID, 'display_name' => $nickname ) );
}
// Set channel ID
if ( $new || $merged ) {
fictioneer_update_user_meta( $wp_user->ID, "fictioneer_{$channel}_id_hash", $channel_id );
}
// Update avatar
if ( $avatar !== $current_avatar && ! get_the_author_meta( 'fictioneer_lock_avatar', $wp_user->ID ) ) {
fictioneer_update_user_meta( $wp_user->ID, 'fictioneer_external_avatar_url', $avatar );
}
// Handle Patreon
if ( $channel === 'patreon' ) {
if ( isset( $user_data['tiers'] ) && count( $user_data['tiers'] ) > 0 ) {
fictioneer_update_user_meta( $wp_user->ID, 'fictioneer_patreon_tiers', $user_data['tiers'] );
} else {
delete_user_meta( $wp_user->ID, 'fictioneer_patreon_tiers' );
}
if ( isset( $user_data['membership'] ) && ! empty( $user_data['membership'] ) ) {
fictioneer_update_user_meta( $wp_user->ID, 'fictioneer_patreon_membership', $user_data['membership'] );
} else {
delete_user_meta( $wp_user->ID, 'fictioneer_patreon_membership' );
}
}
// Login
if ( ! is_user_logged_in() ) {
wp_clear_auth_cookie();
wp_set_current_user( $wp_user->ID );
// Allow login to last three days
add_filter( 'auth_cookie_expiration', function( $length ) {
return FICTIONEER_OAUTH_COOKIE_EXPIRATION;
});
// Set authentication cookie
wp_set_auth_cookie( $wp_user->ID, true );
}
// Action
do_action(
'fictioneer_after_oauth_user',
$wp_user,
array(
'channel' => $user_data['channel'],
'uid' => $user_data['uid'],
'username' => $user_data['username'],
'nickname' => $user_data['nickname'],
'email' => $user_data['email'],
'avatar_url' => $user_data['avatar'],
'patreon_tiers' => $user_data['tiers'] ?? [],
'patreon_membership' => $user_data['membership'] ?? [],
'new' => $new,
'merged' => $merged
)
);
// Return result
if ( is_user_logged_in() ) {
if ( $new ) {
return 'new_user';
}
if ( $merged ) {
return 'merged_user';
}
return 'known_user';
}
}
// Something went very wrong
return 'no_user_found_or_created';
}
// =============================================================================
// HELPERS
// =============================================================================
/**
* Terminate the OAuth 2.0 script and redirect back
*
* @since 5.19.0
*
* @param string $return_url Optional. URL to return to.
* @param array $query_args Optional. Additional query arguments for the redirect.
* @param string $anchor Optional. Anchor for the return URL.
*/
function fictioneer_oauth2_terminate( $return_url = null, $query_args = [], $anchor = null ) {
// Delete cookie
fictioneer_oauth2_delete_cookie();
// Redirect and terminate
wp_safe_redirect( add_query_arg( $query_args, $return_url ?? home_url() ) . ( $anchor ? '#' . $anchor : '' ) );
exit;
}
/**
* Outputs a formatted error message and stops the script
*
* @since 5.5.2
* @since 5.7.5 - Refactored.
* @since 5.19.0 - Refactored again.
*
* @param string $message The error message.
* @param string $title Optional. Title of the error page. Defaults to 'Error'.
*/
function fictioneer_oauth_die( $message, $title = 'Error' ) {
// Delete cookie
fictioneer_oauth2_delete_cookie();
wp_die(
'<h1 style="margin-top: 0;">' . $title . '</h1>' .
'<p><pre>' . print_r( $message, true ) . '</pre></p>' .
'<p>The good news is, nothing has happened to your account. The bad new is, something is not working. Please try again later or contact an administrator for help. <a href="' . home_url() . '">Back to site</a></p>',
$title
);
}
/**
* Get the OAuth 2.0 cookie contents
*
* @since 5.19.0
*
* @return array|null The cookie as array or null on failure.
*/
function fictioneer_oauth2_get_cookie() {
if ( isset( $_COOKIE['fictioneer_oauth'] ) ) {
return fictioneer_decrypt( $_COOKIE['fictioneer_oauth'] ) ?: null;
}
return null;
}
/**
* Deleted the OAuth 2.0 cookie
*
* @since 5.19.0
*/
function fictioneer_oauth2_delete_cookie() {
setcookie(
'fictioneer_oauth',
'',
array(
'expires' => time() - 3600,
'path' => '/',
'domain' => '',
'secure' => is_ssl(),
'httponly' => true,
'samesite' => 'Lax' // Necessary because of redirects
)
);
}
/**
* Get code from OAuth 2.0 provider
*
* @since 5.19.0
*
* @param array $args {
* Array of arguments.
*
* @type string $channel The targeted provider.
* @type string $return_url The return URL.
* @type string $anchor Optional. Anchor for the return URL.
* @type bool $merge Optional. Whether this is a merge request.
* @type int $merge_id ID of the current user or 0.
* }
*/
function fictioneer_oauth2_get_code( $args ) {
// Setup
$client_id = get_option( "fictioneer_{$args['channel']}_client_id" );
$client_secret = get_option( "fictioneer_{$args['channel']}_client_secret" );
// Abort if...
if ( ! $client_id || ! $client_secret ) {
fictioneer_oauth2_terminate( $args['return_url'] );
}
// Prepare request
$params = array(
'response_type' => 'code',
'client_id' => $client_id,
'state' => hash( 'sha256', microtime( TRUE ) . random_bytes( 15 ) . $_SERVER['REMOTE_ADDR'] ),
'scope' => FCN_OAUTH2_API_ENDPOINTS[ $args['channel'] ]['scope'],
'redirect_uri' => get_site_url( null, FICTIONEER_OAUTH_ENDPOINT ),
'force_verify' => 'true',
'prompt' => 'consent',
'access_type' => 'offline'
);
// Set cookie
$value = fictioneer_encrypt(
array(
'state' => $params['state'],
'channel' => $args['channel'],
'return_url' => $args['return_url'],
'anchor' => $args['anchor'],
'merge' => $args['merge'],
'merge_id' => $args['merge_id']
)
);
if ( $value ) {
setcookie(
'fictioneer_oauth',
$value,
array(
'expires' => time() + 300,
'path' => '/',
'domain' => '',
'secure' => is_ssl(),
'httponly' => true,
'samesite' => 'Lax' // Necessary because of redirects
)
);
}
// Request code
wp_redirect( add_query_arg( $params, FCN_OAUTH2_API_ENDPOINTS[ $args['channel'] ]['login'] ) );
// Terminate
exit();
}
/**
* Get the OAuth 2.0 access token
*
* @since 5.19.0
*
* @param string $url URL to make the API request to.
* @param array $body Post body.
* @param array $headers Optional. Array of additional header arguments.
*
* @return object|WP_Error Decoded JSON result or WP_Error on failure.
*/
function fictioneer_oauth2_get_token( $url, $body, $headers = [] ) {
// Params
$args = array(
'headers' => array_merge( array( 'Accept' => 'application/json' ), $headers ),
'body' => $body,
'timeout' => 10,
'blocking' => true,
'reject_unsafe_urls' => true,
'data_format' => 'body',
'limit_response_size' => 4096
);
// Request
$response = wp_remote_post( $url, $args );
// Error?
if ( is_wp_error( $response ) ) {
return $response;
}
// Return decoded body
return json_decode( wp_remote_retrieve_body( $response ) );
}
/**
* Revoke an OAuth 2.0 access token
*
* @since 4.0.0
* @since 5.7.5 - Refactored.
* @since 5.19.0 - Refactored again.
*
* @param string $url URL to make the API request to.
* @param array $post Post body.
*
* @return string HTTP response code.
*/
function fictioneer_oauth2_revoke_token( $url, $body ) {
// Params
$args = array(
'headers' => array( 'Content-Type' => 'application/x-www-form-urlencoded' ),
'body' => $body
);
// Request
$response = wp_remote_post( $url, $args );
// Error?
if ( is_wp_error( $response ) ) {
return 500; // Internal Server Error
}
// Return response code
return wp_remote_retrieve_response_code( $response );
}
/**
* Retrieve and decode user data response
*
* @since 5.19.0
*
* @param object $response API response.
*
* @return object User data object.
*/
function fictioneer_oauth2_retrieve_user_body( $response ) {
// Check retrieved response
if ( is_wp_error( $response ) ) {
fictioneer_oauth_die( implode( '<br>', $response->get_error_messages() ) );
} else {
$user = json_decode( wp_remote_retrieve_body( $response ) );
}
// User data successfully retrieved?
if ( empty( $user ) || json_last_error() !== JSON_ERROR_NONE ) {
fictioneer_oauth_die( wp_remote_retrieve_body( $response ) );
}
// Error?
$response_code = wp_remote_retrieve_response_code( $response );
if ( $response_code < 200 || $response_code >= 300 ) {
fictioneer_oauth_die( wp_remote_retrieve_body( $response ), 'Error ' . $response );
}
// All clear
return $user;
}
// =============================================================================
// PROVIDERS
// =============================================================================
/**
* Perform OAuth 2.0 authentication for Patreon
*
* @since 5.19.0
*
* @param object $token_response Token request response.
* @param array $cookie Decrypted data of the cookie.
*
* @return string Result code from fictioneer_oauth2_make_user().
*/
function fictioneer_oauth2_patreon( $token_response, $cookie ) {
// Build params
$params = '?fields' . urlencode( '[user]' ) . '=email,first_name,image_url,is_email_verified';
$params .= '&fields' . urlencode( '[tier]' ) . '=title,amount_cents,published,description';
$params .= '&fields' . urlencode( '[member]' ) . '=lifetime_support_cents,campaign_lifetime_support_cents,last_charge_date,last_charge_status,next_charge_date,patron_status';
$params .= '&include=memberships.currently_entitled_tiers';
// Retrieve user data from Patreon
$user_response = wp_remote_get(
FCN_OAUTH2_API_ENDPOINTS['patreon']['user'] . $params,
array(
'headers' => array(
'Authorization' => 'Bearer ' . $token_response->access_token
)
)
);
// Check retrieved user response
$user = fictioneer_oauth2_retrieve_user_body( $user_response );
if ( ! isset( $user->data ) ) {
fictioneer_oauth_die( 'Data node not found.' );
}
if ( ! isset( $user->data->attributes ) ) {
fictioneer_oauth_die( 'Attributes node not found.' );
}
if ( ! isset( $user->data->attributes->is_email_verified ) || ! $user->data->attributes->is_email_verified ) {
fictioneer_oauth_die( 'Email not verified.' );
}
// Extract membership data
$tiers = [];
$tier_ids = [];
$membership = [];
if ( isset( $user->included ) ) {
// Tiers data
foreach ( $user->included as $node ) {
if ( isset( $node->type ) && $node->type === 'tier' ) {
$tiers[ $node->id ] = array(
'tier' => sanitize_text_field( $node->attributes->title ),
'title' => sanitize_text_field( $node->attributes->title ),
'description' => wp_kses_post( $node->attributes->description ?? '' ),
'published' => filter_var( $node->attributes->published ?? 0, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ),
'amount_cents' => absint( $node->attributes->amount_cents ?? 0 ),
'timestamp' => current_time( 'U', true ),
'id' => $node->id
);
$tier_ids[] = $node->id;
}
}
// Membership data
foreach ( $user->included as $node ) {
if (
isset( $node->type ) &&
$node->type === 'member' &&
isset( $node->attributes ) &&
isset( $node->relationships->currently_entitled_tiers->data ) &&
in_array( $node->relationships->currently_entitled_tiers->data[0]->id, $tier_ids )
) {
$membership['lifetime_support_cents'] = $node->attributes->lifetime_support_cents ?? $node->attributes->campaign_lifetime_support_cents ?? 0;
$membership['last_charge_date'] = $node->attributes->last_charge_date ?? null;
$membership['last_charge_status'] = $node->attributes->last_charge_status ?? null;
$membership['next_charge_date'] = $node->attributes->next_charge_date ?? null;
$membership['patron_status'] = $node->attributes->patron_status ?? null;
break;
}
}
}
/* Patreon does not have a function to revoke a token (2024/05/21). */
// Login or register user
return fictioneer_oauth2_make_user(
array(
'channel' => 'patreon',
'uid' => $user->data->id,
'email' => $user->data->attributes->email,
'username' => $user->data->attributes->first_name,
'nickname' => $user->data->attributes->first_name,
'avatar' => esc_url_raw( $user->data->attributes->image_url ?? '' ),
'tiers' => $tiers,
'membership' => $membership
),
$cookie
);
}
/**
* Perform OAuth 2.0 authentication for Discord
*
* @since 5.19.0
*
* @param object $token_response Token request response.
* @param array $cookie Decrypted data of the cookie.
*
* @return string Result code from fictioneer_oauth2_make_user().
*/
function fictioneer_oauth2_discord( $token_response, $cookie ) {
// Get user data
$user_response = wp_remote_get(
FCN_OAUTH2_API_ENDPOINTS['discord']['user'],
array(
'headers' => array(
'Authorization' => 'Bearer ' . $token_response->access_token,
'Client-ID' => get_option( 'fictioneer_discord_client_id' )
)
)
);
// Check retrieved user response
$user = fictioneer_oauth2_retrieve_user_body( $user_response );
// Account verified?
if ( ! isset( $user->verified ) || ! $user->verified ) {
fictioneer_oauth_die( 'Account not verified.' );
}
// Revoke token since it's not needed anymore
fictioneer_oauth2_revoke_token(
FCN_OAUTH2_API_ENDPOINTS['discord']['revoke'],
array(
'token' => $token_response->access_token,
'token_type_hint' => 'access_token',
'client_id' => get_option( 'fictioneer_discord_client_id' ),
'client_secret' => get_option( 'fictioneer_discord_client_secret' )
)
);
// Login or register user
return fictioneer_oauth2_make_user(
array(
'channel' => 'discord',
'uid' => $user->id,
'email' => $user->email,
'username' => $user->username . ( $user->discriminator ?? ''),
'nickname' => $user->username,
'avatar' => esc_url_raw( "https://cdn.discordapp.com/avatars/{$user->id}/{$user->avatar}.png" )
),
$cookie
);
}
/**
* Perform OAuth 2.0 authentication for Google
*
* @since 5.19.0
*
* @param object $token_response Token request response.
* @param array $cookie Decrypted data of the cookie.
*
* @return string Result code from fictioneer_oauth2_make_user().
*/
function fictioneer_oauth2_google( $token_response, $cookie ) {
// Retrieve user data from Google
$user_response = wp_remote_get(
FCN_OAUTH2_API_ENDPOINTS['google']['user'],
array(
'headers' => array(
'Authorization' => 'Bearer ' . $token_response->access_token,
'Client-ID' => get_option( 'fictioneer_google_client_id' )
)
)
);
// Check retrieved user response
$user = fictioneer_oauth2_retrieve_user_body( $user_response );
// Account verified?
if ( ! isset( $user->verified_email ) || ! $user->verified_email ) {
fictioneer_oauth_die( 'Email not verified.' );
}
// Revoke token since it's not needed anymore
fictioneer_oauth2_revoke_token(
FCN_OAUTH2_API_ENDPOINTS['google']['revoke'],
array(
'token' => $token_response->access_token
)
);
// Generate random username (because Google uses real names)
$name = fictioneer_get_random_username();
// Login or register user
return fictioneer_oauth2_make_user(
array(
'channel' => 'google',
'uid' => $user->id,
'email' => $user->email,
'username' => $name,
'nickname' => $name,
'avatar' => esc_url_raw( $user->picture ?? '' )
),
$cookie
);
}
/**
* Perform OAuth 2.0 authentication for Twitch
*
* @since 5.19.0
*
* @param object $token_response Token request response.
* @param array $cookie Decrypted data of the cookie.
*
* @return string Result code from fictioneer_oauth2_make_user().
*/
function fictioneer_oauth2_twitch( $token_response, $cookie ) {
// Retrieve user data from Twitch
$user_response = wp_remote_get(
FCN_OAUTH2_API_ENDPOINTS['twitch']['user'],
array(
'headers' => array(
'Authorization' => 'Bearer ' . $token_response->access_token,
'Client-ID' => get_option( 'fictioneer_twitch_client_id' )
)
)
);
// Check retrieved user response
$user = fictioneer_oauth2_retrieve_user_body( $user_response );
// Revoke token since it's not needed anymore
fictioneer_oauth2_revoke_token(
FCN_OAUTH2_API_ENDPOINTS['twitch']['revoke'],
array(
'token' => $token_response->access_token,
'token_type_hint' => 'access_token',
'client_id' => get_option( 'fictioneer_twitch_client_id' )
)
);
// Login or register user
return fictioneer_oauth2_make_user(
array(
'channel' => 'twitch',
'uid' => $user->data[0]->id,
'email' => $user->data[0]->email,
'username' => $user->data[0]->login,
'nickname' => $user->data[0]->display_name,
'avatar' => esc_url_raw( $user->data[0]->profile_image_url ?? '' )
),
$cookie
);
}
/**
* Perform OAuth 2.0 authentication for SubscribeStar
*
* @since 5.19.0
*
* @param object $token_response Token request response.
* @param array $cookie Decrypted data of the cookie.
*
* @return string Result code from fictioneer_oauth2_make_user().
*/
function fictioneer_oauth2_subscribestar( $token_response, $cookie ) {
// GraphQL query
$query = "{ user { id name email email_verified avatar_url } subscriber { subscription { price } } }";
// Retrieve user data from SubscribeStar
$user_response = wp_remote_post(
FCN_OAUTH2_API_ENDPOINTS['subscribestar']['api'],
array(
'headers' => array(
'Accept' => 'application/json',
'Authorization' => 'Bearer ' . $token_response->access_token
),
'body' => array( 'query' => $query ),
'blocking' => true,
'reject_unsafe_urls' => true,
'data_format' => 'body'
)
);
fictioneer_oauth2_delete_cookie();
wp_die( json_encode( $user_response ) );
}