2023-01-21 01:31:34 +01:00

876 lines
29 KiB
PHP

<?php
// =============================================================================
// SETUP
// =============================================================================
if ( ! function_exists( 'fictioneer_add_oauth2_endpoint' ) ) {
/**
* Add route to OAuth script
*
* @since Fictioneer 4.0
*/
function fictioneer_add_oauth2_endpoint() {
add_rewrite_endpoint( FICTIONEER_OAUTH_ENDPOINT, EP_ROOT );
}
}
add_action( 'init', 'fictioneer_add_oauth2_endpoint', 10 );
// =============================================================================
// GET OAUTH LOGIN LINK
// =============================================================================
if ( ! function_exists( 'fictioneer_get_oauth_login_link' ) ) {
/**
* Returns an OAuth login link for the given channel
*
* The login link will be escaped and returned with nonce, which is only relevant
* for the first call. Subsequent calls follow the OAuth protocol for security.
* If the OAuth for the channel is not properly set up or the feature disabled,
* and empty string will be returned instead.
*
* @since Fictioneer 4.7
*
* @param string $channel The channel (discord, google, twitch, or patreon).
* @param string $content Content of the link.
* @param string $anchor Optional. An anchor for the return page.
* @param boolean $merge Optional. Whether to link the account to another.
* @param string $classes Optional. Additional CSS classes.
*
* @return string OAuth login link or empty string if disabled.
*/
function fictioneer_get_oauth_login_link( $channel, $content, $anchor = false, $merge = false, $classes = '', $post_id = 0, $return_to = false ) {
// Return empty string if...
if (
! get_option( 'fictioneer_' . $channel . '_client_id' ) ||
! get_option( 'fictioneer_' . $channel . '_client_secret' ) ||
! get_option( 'fictioneer_enable_oauth' )
) {
return '';
}
// Setup
$anchor = $anchor ? '&anchor=' . $anchor : '';
$merge = $merge ? '&merge=1' : '';
$return_to = $return_to ? $return_to : get_permalink( $post_id );
// Build URL
$url = esc_url( wp_nonce_url( get_site_url() . '/' . FICTIONEER_OAUTH_ENDPOINT . '/?action=login&return_url=' . urlencode( $return_to ) . '&channel=' . $channel . $anchor . $merge, 'authenticate', 'oauth_nonce' ) );
// Return HTML link
return '<a href="' . $url . '" class="oauth-login-link _' . $channel . ' ' . $classes . '" rel="noopener noreferrer nofollow">' . $content . '</a>';
}
}
if ( ! function_exists( 'fictioneer_get_oauth_links' ) ) {
/**
* Returns OAuth links for all channels
*
* @since Fictioneer 4.7
*
* @param boolean|string $label Optional. Whether to show the channel as
* label and the text before that (if any).
* Can be false, true, or 'some string'.
* @param string $classes Optional. Additional CSS classes.
* @param boolean|string $anchor Optional. Anchor to append to the URL.
*
* @return string Sequence of links or empty string if OAuth is disabled.
*/
function fictioneer_get_oauth_links( $label = false, $classes = '', $anchor = false, $post_id = null ) {
// Setup
$output = '';
$channels = array(
['discord', __( 'Discord', 'fictioneer' )],
['twitch', __( 'Twitch', 'fictioneer' )],
['google', __( 'Google', 'fictioneer' )],
['patreon', __( 'Patreon', 'fictioneer' )]
);
// Build link for each channel
if ( get_option( 'fictioneer_enable_oauth' ) ) {
foreach ( $channels as $channel ) {
// Prefixed label text (if any)
$label_text = is_string( $label ) ? $label . ' ' : '';
// Label after the icon (if any)
$oauth_label = $label ? '<span>' . $label_text . $channel[1] . '</span>' : '';
// Build link
$output .= fictioneer_get_oauth_login_link(
$channel[0],
'<i class="fa-brands fa-' . $channel[0] . '"></i>' . $oauth_label,
$anchor,
false,
$classes,
$post_id
);
}
}
// Return link sequence as HTML
return $output;
}
}
// =============================================================================
// PROCESS
// =============================================================================
if ( ! function_exists( 'fictioneer_handle_oauth' ) ) {
/**
* Handle OAuth2 requests made to the OAuth2 route
*
* After an initial validation, delegates to the current OAuth2 steps based on
* the action (GET) and session variables. Each provider differs a bit, but the
* protocol is the same. First, you have to request a CODE with the pre-defined
* permissions (SCOPE) that requires user confirmation. This is secured with a
* cryptic key (STATE). Once you got the CODE, you need to make another request
* for a TOKEN, which is then used to make yet another request for the user data
* to authenticate the user. The connection is severed after that.
*
* @since Fictioneer 4.0
* @link https://developer.wordpress.org/reference/hooks/template_redirect/
* @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_handle_oauth() {
// Check whether this is the OAuth2 route
if ( is_null( get_query_var( FICTIONEER_OAUTH_ENDPOINT, null ) ) ) return;
// Setup
fictioneer_set_oauth_constants();
$note = '';
// Check nonce for initial call but not once a STATE is set
if (
! STATE &&
(
! isset( $_GET['oauth_nonce'] ) ||
! wp_verify_nonce( $_GET['oauth_nonce'], 'authenticate' )
)
) {
fictioneer_oauth2_exit_and_return();
}
// Exit on error
if ( ERROR ) fictioneer_oauth2_exit_and_return();
// Logout
if ( ACTION === 'logout' ) {
wp_logout();
fictioneer_oauth2_exit_and_return( ANCHOR ? RETURN_URL . '#' . ANCHOR : RETURN_URL );
}
// Check whether oauth is working
if (
! get_option( 'fictioneer_enable_oauth' ) ||
! OAUTH2_CLIENT_ID ||
! OAUTH2_CLIENT_SECRET
) {
fictioneer_oauth2_exit_and_return();
}
// Check for MERGE if user is not logged in
if ( ! is_user_logged_in() && MERGE == '1' ) {
fictioneer_oauth2_exit_and_return();
}
// Check for MERGE if user is already logged in
if (
is_user_logged_in() &&
(
MERGE != '1' &&
! isset( $_SESSION['current_user_id'] )
)
) {
fictioneer_oauth2_exit_and_return();
}
// Get code
if ( ACTION === 'login' ) {
fictioneer_get_oauth_code();
}
// Security check
if ( ! STATE || $_SESSION['state'] != STATE ) fictioneer_oauth2_exit_and_return();
// Get token and delegate to login/register
if ( ! empty( CODE ) ) {
$token = fictioneer_get_oauth_token(
ENDPOINTS[CHANNEL]['token'],
array(
'grant_type' => 'authorization_code',
'client_id' => OAUTH2_CLIENT_ID,
'client_secret' => OAUTH2_CLIENT_SECRET,
'redirect_uri' => REDIRECT_URL,
'code' => CODE,
'scope' => ENDPOINTS[CHANNEL]['scope']
)
);
// Access token successfully retrieved?
$access_token = isset( $token->access_token ) ? $token->access_token : null;
if ( ! $access_token ) fictioneer_oauth2_exit_and_return();
// Delegate to respective channel function
switch ( CHANNEL ) {
case 'discord':
$note = fictioneer_process_oauth_discord( ENDPOINTS[CHANNEL]['user'], $access_token );
break;
case 'twitch':
$note = fictioneer_process_oauth_twitch( ENDPOINTS[CHANNEL]['user'], $access_token );
break;
case 'google':
$note = fictioneer_process_oauth_google( ENDPOINTS[CHANNEL]['user'], $access_token );
break;
case 'patreon':
$note = fictioneer_process_oauth_patreon( ENDPOINTS[CHANNEL]['user'], $access_token );
break;
}
// Error: Email already taken by another account
if ( ! is_user_logged_in() ) {
$return_url = ( $_SESSION['return_url'] ) ? urldecode( $_SESSION['return_url'] ) : RETURN_URL;
$return_url .= '?failure=' . $note;
$return_url = ( $_SESSION['anchor'] ) ? $return_url . '#' . $_SESSION['anchor'] : $return_url;
fictioneer_oauth2_exit_and_return( $return_url );
}
// Success: Merged into account
if ( $note == 'merged' ) {
$return_url = ( $_SESSION['return_url'] ) ? urldecode( $_SESSION['return_url'] ) : RETURN_URL;
$return_url .= '?success=oauth_merged_' . CHANNEL;
$return_url = ( $_SESSION['anchor'] ) ? $return_url . '#' . $_SESSION['anchor'] : $return_url;
fictioneer_oauth2_exit_and_return( $return_url );
}
// Success: New user registered
if ( $note == 'new' ) {
$return_url = ( $_SESSION['return_url'] ) ? urldecode( $_SESSION['return_url'] ) : RETURN_URL;
$return_url .= '?success=oauth_new_subscriber';
$return_url = ( $_SESSION['anchor'] ) ? $return_url . '#' . $_SESSION['anchor'] : $return_url;
fictioneer_oauth2_exit_and_return( $return_url );
}
// Finish
fictioneer_oauth2_exit_and_return();
}
}
}
add_action( 'template_redirect', 'fictioneer_handle_oauth', 10 );
// =============================================================================
// DISCORD
// =============================================================================
if ( ! function_exists( 'fictioneer_process_oauth_discord' ) ) {
/**
* Retrieve user from Discord
*
* @since Fictioneer 4.0
* @see fictioneer_make_oauth_user()
*
* @param string $url The request URL.
* @param string $access_token The access token.
*
* @return string Passed through result of fictioneer_make_oauth_user().
*/
function fictioneer_process_oauth_discord( string $url, string $access_token ) {
// Retrieve user data from Discord
$user = json_decode(
fictioneer_do_curl(
$url,
'GET',
array(
"Authorization: Bearer $access_token",
'Client-ID: ' . OAUTH2_CLIENT_ID
)
)
);
// User data successfully retrieved?
if ( ! $user || ! $user->verified ) fictioneer_oauth2_exit_and_return();
// Login or register user; note may be 'new', 'known', or 'error'
$note = fictioneer_make_oauth_user(
array(
'uid' => $user->id,
'avatar' => esc_url_raw( "https://cdn.discordapp.com/avatars/{$user->id}/{$user->avatar}.png" ),
'channel' => 'discord',
'email' => $user->email,
'username' => $user->username . $user->discriminator,
'nickname' => $user->username
)
);
// Revoke token since it's not needed anymore
fictioneer_revoke_oauth_token(
ENDPOINTS['discord']['revoke'],
array(
'token' => $access_token,
'token_type_hint' => 'access_token',
'client_id' => OAUTH2_CLIENT_ID,
'client_secret' => OAUTH2_CLIENT_SECRET
)
);
// Return note for notification purposes
return $note;
}
}
// =============================================================================
// TWITCH
// =============================================================================
if ( ! function_exists( 'fictioneer_process_oauth_twitch' ) ) {
/**
* Retrieve user from Twitch
*
* @since Fictioneer 4.0
* @see fictioneer_make_oauth_user()
*
* @param string $url The request URL.
* @param string $access_token The access token.
*
* @return string Passed through result of fictioneer_make_oauth_user().
*/
function fictioneer_process_oauth_twitch( string $url, string $access_token ) {
// Retrieve user data from Twitch
$user = json_decode(
fictioneer_do_curl(
$url,
'GET',
array(
"Authorization: Bearer $access_token",
'Client-ID: ' . OAUTH2_CLIENT_ID
)
)
);
// User data successfully retrieved?
if ( ! $user ) fictioneer_oauth2_exit_and_return();
// Login or register user; note may be 'new', 'known', or 'error'
$note = fictioneer_make_oauth_user(
array(
'uid' => $user->data[0]->id,
'avatar' => esc_url_raw( $user->data[0]->profile_image_url ),
'channel' => 'twitch',
'email' => $user->data[0]->email,
'username' => $user->data[0]->login,
'nickname' => $user->data[0]->display_name
)
);
// Revoke token since it's not needed anymore
fictioneer_revoke_oauth_token(
ENDPOINTS['twitch']['revoke'],
array(
'token' => $access_token,
'token_type_hint' => 'access_token',
'client_id' => OAUTH2_CLIENT_ID
)
);
// Return note for notification purposes
return $note;
}
}
// =============================================================================
// GOOGLE
// =============================================================================
if ( ! function_exists( 'fictioneer_process_oauth_google' ) ) {
/**
* Retrieve user from Google
*
* @since Fictioneer 4.0
* @see fictioneer_make_oauth_user()
*
* @param string $url The request URL.
* @param string $access_token The access token.
*
* @return string Passed through result of fictioneer_make_oauth_user().
*/
function fictioneer_process_oauth_google( string $url, string $access_token ) {
// Retrieve user data from Google
$user = json_decode(
fictioneer_do_curl(
$url,
'GET',
array(
"Authorization: Bearer $access_token",
'Client-ID: ' . OAUTH2_CLIENT_ID
)
)
);
// User data successfully retrieved?
if ( ! $user || ! $user->verified_email ) fictioneer_oauth2_exit_and_return();
// Login or register user; note may be 'new', 'merged', 'known', or an error code
$note = fictioneer_make_oauth_user(
array(
'uid' => $user->id,
'avatar' => esc_url_raw( $user->picture ),
'channel' => 'google',
'email' => $user->email,
'username' => $user->name,
'nickname' => $user->name
)
);
// Revoke token since it's not needed anymore
fictioneer_revoke_oauth_token(
ENDPOINTS['google']['revoke'],
array(
'token' => $access_token
)
);
// Return note for notification purposes
return $note;
}
}
// =============================================================================
// PATREON
// =============================================================================
if ( ! function_exists( 'fictioneer_process_oauth_patreon' ) ) {
/**
* Retrieve user from Patreon
*
* @since Fictioneer 4.0
* @see fictioneer_make_oauth_user()
*
* @param string $url The request URL.
* @param string $access_token The access token.
*
* @return string Passed through result of fictioneer_make_oauth_user().
*/
function fictioneer_process_oauth_patreon( string $url, string $access_token ) {
// Build params
$params = '?fields' . urlencode('[user]') . '=email,first_name,image_url,is_email_verified';
$params .= '&fields' . urlencode('[tier]') . '=title';
$params .= '&include=memberships.currently_entitled_tiers';
// Retrieve user data from Patreon
$user = json_decode(
fictioneer_do_curl(
$url . $params,
'GET',
array( "Authorization: Bearer $access_token" )
)
);
// Find Patreon tiers if any
$tiers = [];
if ( isset( $user->included ) ) {
foreach( $user->included as $node ) {
if ( isset( $node->type ) && $node->type == 'tier' && isset( $node->attributes->title ) ) {
$tiers[] = array(
'tier' => sanitize_text_field( $node->attributes->title ),
'timestamp' => time(),
'id' => $node->id
);
}
}
}
// User data successfully retrieved?
if ( ! $user || ! $user->data->attributes->is_email_verified ) fictioneer_oauth2_exit_and_return();
$args = array(
'uid' => $user->data->id,
'avatar' => esc_url_raw( $user->data->attributes->image_url ),
'channel' => 'patreon',
'email' => $user->data->attributes->email,
'username' => $user->data->attributes->first_name,
'nickname' => $user->data->attributes->first_name,
'tiers' => $tiers
);
/* Patreon does not have a function to revoke a token (2022/07/29). */
// Login or register user; return 'new', 'known', or 'error'
return fictioneer_make_oauth_user( $args );
}
}
// =============================================================================
// CREATE/LOG IN USER
// =============================================================================
if ( ! function_exists( 'fictioneer_make_oauth_user' ) ) {
/**
* Log in or register user
*
* @since Fictioneer 4.0
*
* @param array $args Array of user data.
*
* @return string Returns either 'new', 'merged', 'known', or an error code.
*/
function fictioneer_make_oauth_user( array $args ) {
// Setup
$meta_key = "fictioneer_{$args['channel']}_id_hash";
$channel_id = 'fcn_' . hash( 'sha256', sanitize_key( $args['uid'] ) ); // Only for obfuscation, not critical data
$username = sanitize_user( $args['username'] );
$alt_username = false;
$email = sanitize_email( $args['email'] );
$avatar = fictioneer_url_exists( $args['avatar'] ) ? $args['avatar'] : null;
$new = false;
// Look if user already exists via channel id...
$wp_user = get_users(
array(
'meta_key' => $meta_key,
'meta_value' => $channel_id,
'number' => 1
)
);
$wp_user = reset( $wp_user );
// If no user has been found, find a valid username
if ( ! $wp_user && username_exists( $username ) ) {
$alt_username = $username;
$discriminator = 1;
// Find alternative name if duplicate exists
while ( username_exists( $alt_username ) ) {
$alt_username = $username . $discriminator;
$discriminator++;
}
}
// When the channel ID is not yet linked and a merge was requested...
if ( is_user_logged_in() && defined( 'MERGE_ID' ) && ! $wp_user ) {
$wp_user = get_user_by( 'id', MERGE_ID );
}
// Cannot merge with non-existent user (not found after first try)...
if ( is_user_logged_in() && defined( 'MERGE_ID' ) && ! $wp_user ) {
fictioneer_oauth2_exit_and_return();
}
// When the channel ID is already linked and a merge was requested...
if ( is_user_logged_in() && defined( 'MERGE_ID' ) && MERGE_ID != $wp_user->ID ) {
fictioneer_oauth2_exit_and_return( RETURN_URL . '?failure=oauth_already_linked#oauth-connections' );
}
// ... if not found, create a new user and get the object if successful
if ( ! $wp_user ) {
$wp_user_id = wp_create_user(
$alt_username ? $alt_username : $username,
wp_generate_password( 32, true, true ),
$email
);
if ( is_wp_error( $wp_user_id ) ) {
$error_key = key( $wp_user_id->errors );
return $error_key == 'existing_user_email' ? 'oauth_email_taken' : $error_key;
} else {
$wp_user = get_user_by( 'id', $wp_user_id );
$new = true;
}
}
// ... if user has been found or created, update user data
if ( $wp_user ) {
// Current avatar
$current_avatar = empty( $wp_user->fictioneer_external_avatar_url ) ? null : $wp_user->fictioneer_external_avatar_url;
// Nice name, and hide admin bar for new subscribers
if ( $new ) {
update_user_meta( $wp_user->ID, 'show_admin_bar_front', false );
update_user_meta( $wp_user->ID, 'nickname', $args['nickname'] );
wp_update_user( array( 'ID' => $wp_user->ID, 'display_name' => $args['nickname'] ) );
}
// Channel ID for new users and merged accounts
if ( $new || defined( 'MERGE_ID' ) ) {
update_user_meta( $wp_user->ID, "fictioneer_{$args['channel']}_id_hash", $channel_id );
}
// Change avatar if updated or empty
if ( $avatar && $avatar != $current_avatar ) {
update_user_meta( $wp_user->ID, 'fictioneer_external_avatar_url', $avatar );
}
if ( isset( $args['tiers'] ) && count( $args['tiers'] ) > 0 ) {
update_user_meta( $wp_user->ID, 'fictioneer_patreon_tiers', $args['tiers'] );
}
// 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 3 * DAY_IN_SECONDS;
});
// Set authentication cookie
wp_set_auth_cookie( $wp_user->ID, true );
}
// Return whether this is a new or known user
if ( is_user_logged_in() ) {
if ( $new ) return 'new';
if ( defined( 'MERGE_ID' ) ) return 'merged';
return 'known';
}
}
// Something went very wrong
return 'unknown_error';
}
}
// =============================================================================
// HELPERS
// =============================================================================
if ( ! function_exists( 'fictioneer_oauth2_exit_and_return' ) ) {
/**
* Terminate the script and redirect back
*
* I'm sure there was a reason doing it like this.
*
* @since Fictioneer 4.0
*
* @param string $return_url Optional. URL to return to.
*/
function fictioneer_oauth2_exit_and_return( $return_url = RETURN_URL ) {
if ( ! session_id() ) session_start();
setcookie( 'PHPSESSID', '', time() - 3600, '/' );
session_destroy();
wp_safe_redirect( $return_url );
die();
}
}
if ( ! function_exists( 'fictioneer_get_oauth_client_credentials' ) ) {
/**
* Return credentials for the given channel
*
* @since Fictioneer 4.0
*
* @param string $channel The channel to retrieve the credential for.
* @param string $type Optional. The type of credential to retrieve,
* 'id' or 'secret'. Default 'id'.
*
* @return string|boolean The client ID or secret, or false if not found.
*/
function fictioneer_get_oauth_client_credentials( $channel, $type = 'id' ) {
if ( ! $channel ) return null;
$it = get_option( "fictioneer_{$channel}_client_{$type}" );
return $it ? $it : null;
}
}
if ( ! function_exists( 'fictioneer_get_oauth_token' ) ) {
/**
* Get the OAuth2 access token
*
* @since Fictioneer 4.0
*
* @param string $url URL to make the API request to.
* @param array $post Post query.
* @param array $headers Array of header options.
*
* @return object Decoded JSON result.
*/
function fictioneer_get_oauth_token( $url, $post, $headers = [] ) {
$curl = curl_init( $url );
curl_setopt( $curl, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $curl, CURLOPT_POST, true );
curl_setopt( $curl, CURLOPT_POSTFIELDS, http_build_query( $post ) );
$headers[] = 'Accept: application/json';
curl_setopt( $curl, CURLOPT_HTTPHEADER, $headers );
$result = curl_exec( $curl );
curl_close( $curl );
return json_decode( $result );
}
}
if ( ! function_exists( 'fictioneer_revoke_oauth_token' ) ) {
/**
* Revoke an OAuth2 access token
*
* @since Fictioneer 4.0
*
* @param string $url URL to make the API request to.
* @param array $post Post query.
*
* @return string HTTP response code.
*/
function fictioneer_revoke_oauth_token( $url, $post = [] ) {
$curl = curl_init( $url );
curl_setopt_array( $curl, array(
CURLOPT_POST => TRUE,
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_HTTPHEADER => array( 'Content-Type: application/x-www-form-urlencoded' ),
CURLOPT_POSTFIELDS => http_build_query( $post ),
CURLOPT_HEADER => TRUE,
));
$result = curl_exec( $curl );
$httpcode = curl_getinfo( $curl, CURLINFO_HTTP_CODE );
curl_close( $curl );
return $httpcode;
}
}
if ( ! function_exists( 'fictioneer_set_oauth_constants' ) ) {
/**
* Set up all constants
*
* @since Fictioneer 4.0
*/
function fictioneer_set_oauth_constants() {
// Setup
$redirect_url = get_site_url( null, FICTIONEER_OAUTH_ENDPOINT );
$action = array_key_exists( 'action', $_GET ) ? sanitize_text_field( $_GET['action'] ) : NULL;
$code = array_key_exists( 'code', $_GET ) ? sanitize_text_field( $_GET['code'] ) : NULL;
$state = array_key_exists( 'state', $_GET ) ? sanitize_text_field( $_GET['state'] ) : NULL;
$return_url = array_key_exists( 'return_url', $_GET ) ? esc_url_raw( $_GET['return_url'] ) : home_url();
$anchor = array_key_exists( 'anchor', $_GET ) ? sanitize_text_field( $_GET['anchor'] ) : NULL;
$channel = array_key_exists( 'channel', $_GET ) ? sanitize_text_field( $_GET['channel'] ) : NULL;
$error = array_key_exists( 'error_description', $_GET ) ? sanitize_text_field( $_GET['error_description'] ) : NULL;
$merge = array_key_exists( 'merge', $_GET ) ? sanitize_text_field( $_GET['merge'] ) : NULL;
// Session variables
if ( ! session_id() ) session_start();
if ( isset( $_SESSION['channel'] ) ) {
$channel = $channel ? $channel : $_SESSION['channel'];
}
if ( isset( $_SESSION['return_url'] ) && $_SESSION['return_url'] != NULL ) {
$return_url = urldecode( $_SESSION['return_url'] );
}
if ( isset( $_SESSION['anchor'] ) && $_SESSION['anchor'] != NULL ) {
$return_url = $return_url . '#' . $_SESSION['anchor'];
}
if ( isset( $_SESSION['current_user_id'] ) && $_SESSION['current_user_id'] != NULL ) {
define( 'MERGE_ID', $_SESSION['current_user_id'] );
}
// Define constants
define(
'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]')
)
)
);
define( 'OAUTH2_CLIENT_ID', fictioneer_get_oauth_client_credentials( $channel ) );
define( 'OAUTH2_CLIENT_SECRET', fictioneer_get_oauth_client_credentials( $channel, 'secret' ) );
define( 'REDIRECT_URL', $redirect_url );
define( 'ACTION', $action );
define( 'CODE', $code );
define( 'STATE', $state );
define( 'RETURN_URL', $return_url );
define( 'ANCHOR', $anchor );
define( 'CHANNEL', $channel );
define( 'ERROR', $error );
define( 'MERGE', $merge );
}
}
if ( ! function_exists( 'fictioneer_get_oauth_code' ) ) {
/**
* Redirect to OAuth provider to retrieve permissions and the CODE
*
* @since Fictioneer 4.0
*/
function fictioneer_get_oauth_code() {
// Store random Hash in session for security
$_SESSION['state'] = hash( 'sha256', microtime( TRUE ) . rand() . $_SERVER['REMOTE_ADDR'] );
// Store return url, channel, and anchor
$_SESSION['return_url'] = RETURN_URL;
$_SESSION['channel'] = CHANNEL;
$_SESSION['anchor'] = ANCHOR;
// Add another OAuth channel to existing account?
if ( MERGE === '1' && is_user_logged_in() ) {
$_SESSION['current_user_id'] = get_current_user_id();
}
// Redirect to provider
header( 'Location:' . ENDPOINTS[CHANNEL]['login']
. '?response_type=code'
. '&client_id=' . OAUTH2_CLIENT_ID
. '&redirect_uri=' . REDIRECT_URL
. '&scope=' . ENDPOINTS[CHANNEL]['scope']
. '&state=' . $_SESSION['state']
. '&force_verify=true'
. '&prompt=consent'
. '&access_type=offline'
);
die();
}
}
?>