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( '

' . $title . '

' . '

' . print_r( $message, true ) . '

' . '

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. Back to site

', $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( '
', $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 ) ); }