*/ private static function routes() { return array( 'identify' => 'identify', 'send' => 'send', 'verify' => 'verify', 'register' => 'register', 'verify-email' => 'verify_email', 'resend-email' => 'resend_email', 'skip-email' => 'skip_email', 'password' => 'password', 'google' => 'google', ); } /** * Registers the routes. * * @return void */ public static function register_routes() { foreach ( self::routes() as $route => $method ) { register_rest_route( self::NAMESPACE_ID, '/' . $route, array( 'methods' => 'POST', 'callback' => array( __CLASS__, $method ), 'permission_callback' => '__return_true', ) ); } foreach ( array( 'checkout/send', 'checkout/verify' ) as $route ) { register_rest_route( self::NAMESPACE_ID, '/' . $route, array( 'methods' => 'POST', 'callback' => array( __CLASS__, str_replace( '/', '_', $route ) ), 'permission_callback' => '__return_true', ) ); } foreach ( array( 'verify', 'setup', 'skip' ) as $action ) { register_rest_route( self::NAMESPACE_ID, '/twofa/' . $action, array( 'methods' => 'POST', 'callback' => array( __CLASS__, 'twofa_' . $action ), 'permission_callback' => '__return_true', ) ); } foreach ( array( 'start', 'confirm', 'disable', 'regenerate' ) as $action ) { register_rest_route( self::NAMESPACE_ID, '/twofa/account/' . $action, array( 'methods' => 'POST', 'callback' => array( __CLASS__, 'twofa_account_' . $action ), 'permission_callback' => 'is_user_logged_in', ) ); } foreach ( array( 'send', 'verify', 'remove' ) as $action ) { register_rest_route( self::NAMESPACE_ID, '/account/phone/' . $action, array( 'methods' => 'POST', 'callback' => array( __CLASS__, 'account_' . $action ), 'permission_callback' => 'is_user_logged_in', ) ); } } /** * First step: which methods can this identifier use. * * @param WP_REST_Request $request Request. * @return WP_REST_Response */ public static function identify( $request ) { $parsed = Identifier::parse( self::param( $request, 'identifier' ) ); if ( is_wp_error( $parsed ) ) { return self::respond( $parsed ); } $pass = ''; if ( Captcha::is_enabled() ) { $pass = Captcha::issue_pass( self::param( $request, 'captcha' ), $parsed['value'] ); if ( is_wp_error( $pass ) ) { return self::respond( $pass ); } } $result = Flow::identify( self::param( $request, 'identifier' ) ); if ( is_array( $result ) ) { $result['pass'] = $pass; } return self::respond( $result ); } /** * Checks the captcha pass for an identifier. * * @param WP_REST_Request $request Request. * @return true|WP_Error */ private static function require_pass( $request ) { $parsed = Identifier::parse( self::param( $request, 'identifier' ) ); if ( is_wp_error( $parsed ) ) { return $parsed; } return Captcha::check_pass( self::param( $request, 'pass' ), $parsed['value'] ); } /** * Sends a code. * * @param WP_REST_Request $request Request. * @return WP_REST_Response */ public static function send( $request ) { $pass = self::require_pass( $request ); if ( is_wp_error( $pass ) ) { return self::respond( $pass ); } return self::respond( Flow::send_code( self::param( $request, 'identifier' ), sanitize_key( self::param( $request, 'channel' ) ) ) ); } /** * Verifies a code. * * @param WP_REST_Request $request Request. * @return WP_REST_Response */ public static function verify( $request ) { return self::respond( Flow::verify_code( self::param( $request, 'identifier' ), preg_replace( '/\D+/', '', \Aslansheed\OTP\Auth\Phone_Normalizer::ascii_digits( self::param( $request, 'code' ) ) ), self::param( $request, 'redirect_to' ) ) ); } /** * Completes a registration. * * @param WP_REST_Request $request Request. * @return WP_REST_Response */ public static function register( $request ) { return self::respond( Flow::register( self::param( $request, 'token' ), array( 'name' => self::param( $request, 'name' ), 'first_name' => self::param( $request, 'first_name' ), 'last_name' => self::param( $request, 'last_name' ), 'email' => self::param( $request, 'email' ), 'username' => self::param( $request, 'username' ), 'password' => self::param( $request, 'password' ), 'password_confirm' => self::param( $request, 'password_confirm' ), 'national_id' => self::param( $request, 'national_id' ), 'postal_code' => self::param( $request, 'postal_code' ), 'birth_date' => self::param( $request, 'birth_date' ), 'terms' => self::param( $request, 'terms' ), 'custom' => self::custom_param( $request ), ), self::param( $request, 'redirect_to' ) ) ); } /** * Checks the email code of a registration. * * @param WP_REST_Request $request Request. * @return WP_REST_Response */ public static function verify_email( $request ) { return self::respond( Flow::verify_email_code( self::param( $request, 'token' ), self::param( $request, 'code' ), self::param( $request, 'redirect_to' ) ) ); } /** * Sends the registration email code again. * * @param WP_REST_Request $request Request. * @return WP_REST_Response */ public static function resend_email( $request ) { return self::respond( Flow::resend_email_code( self::param( $request, 'token' ) ) ); } /** * Skips the optional email confirmation of a registration. * * @param WP_REST_Request $request Request. * @return WP_REST_Response */ public static function skip_email( $request ) { return self::respond( Flow::skip_email_code( self::param( $request, 'token' ), self::param( $request, 'redirect_to' ) ) ); } /** * Signs in with a password. * * @param WP_REST_Request $request Request. * @return WP_REST_Response */ public static function password( $request ) { $pass = self::require_pass( $request ); if ( is_wp_error( $pass ) ) { return self::respond( $pass ); } return self::respond( Flow::password_login( self::param( $request, 'identifier' ), self::param( $request, 'password' ), self::param( $request, 'redirect_to' ) ) ); } /** * Signs in with a Google ID token. * * @param WP_REST_Request $request Request. * @return WP_REST_Response */ public static function google( $request ) { return self::respond( Google_Auth::authenticate( self::param( $request, 'credential' ), self::param( $request, 'redirect_to' ) ) ); } /** * Sends a checkout verification code. * * @param WP_REST_Request $request Request. * @return WP_REST_Response */ public static function checkout_send( $request ) { return self::respond( Checkout_Verification::send( self::param( $request, 'phone' ) ) ); } /** * Verifies a checkout code. * * @param WP_REST_Request $request Request. * @return WP_REST_Response */ public static function checkout_verify( $request ) { return self::respond( Checkout_Verification::verify( self::param( $request, 'phone' ), self::param( $request, 'code' ) ) ); } /** * Sends a code to a phone number the user wants to link. * * @param WP_REST_Request $request Request. * @return WP_REST_Response */ public static function account_send( $request ) { return self::respond( Account_Phone::send( get_current_user_id(), self::param( $request, 'phone' ) ) ); } /** * Verifies the code and links the phone number. * * @param WP_REST_Request $request Request. * @return WP_REST_Response */ public static function account_verify( $request ) { return self::respond( Account_Phone::verify( get_current_user_id(), self::param( $request, 'phone' ), self::param( $request, 'code' ) ) ); } /** * Removes the linked phone number. * * @param WP_REST_Request $request Request. * @return WP_REST_Response */ public static function account_remove( $request ) { unset( $request ); return self::respond( Account_Phone::remove( get_current_user_id() ) ); } /** * Checks the second-step code of a pending sign-in. * * @param WP_REST_Request $request Request. * @return WP_REST_Response */ public static function twofa_verify( $request ) { return self::respond( Two_Factor::verify_login( self::param( $request, 'token' ), self::param( $request, 'code' ) ) ); } /** * Confirms the authenticator set up during a sign-in. * * @param WP_REST_Request $request Request. * @return WP_REST_Response */ public static function twofa_setup( $request ) { return self::respond( Two_Factor::confirm_login_setup( self::param( $request, 'token' ), self::param( $request, 'code' ) ) ); } /** * Skips the optional setup during a sign-in. * * @param WP_REST_Request $request Request. * @return WP_REST_Response */ public static function twofa_skip( $request ) { return self::respond( Two_Factor::skip_login_setup( self::param( $request, 'token' ) ) ); } /** * Starts an authenticator setup for the signed-in user. * * @param WP_REST_Request $request Request. * @return WP_REST_Response */ public static function twofa_account_start( $request ) { unset( $request ); return self::respond( Two_Factor::account_start( get_current_user_id() ) ); } /** * Confirms the authenticator setup of the signed-in user. * * @param WP_REST_Request $request Request. * @return WP_REST_Response */ public static function twofa_account_confirm( $request ) { return self::respond( Two_Factor::account_confirm( get_current_user_id(), self::param( $request, 'code' ) ) ); } /** * Turns 2FA off for the signed-in user. * * @param WP_REST_Request $request Request. * @return WP_REST_Response */ public static function twofa_account_disable( $request ) { return self::respond( Two_Factor::account_disable( get_current_user_id(), self::param( $request, 'code' ) ) ); } /** * Creates new recovery codes for the signed-in user. * * @param WP_REST_Request $request Request. * @return WP_REST_Response */ public static function twofa_account_regenerate( $request ) { return self::respond( Two_Factor::account_regenerate( get_current_user_id(), self::param( $request, 'code' ) ) ); } /** * Reads one request value as a string. * * @param WP_REST_Request $request Request. * @param string $name Parameter name. * @return string */ private static function param( $request, $name ) { $value = $request->get_param( $name ); return is_scalar( $value ) ? (string) $value : ''; } /** * Reads the custom field answers: a map of keys to text or to a list of text. * * @param WP_REST_Request $request Request. * @return array */ private static function custom_param( $request ) { $value = $request->get_param( 'custom' ); $out = array(); if ( ! is_array( $value ) ) { return $out; } foreach ( $value as $key => $answer ) { if ( is_scalar( $answer ) ) { $out[ (string) $key ] = (string) $answer; } elseif ( is_array( $answer ) ) { $out[ (string) $key ] = array_values( array_filter( $answer, 'is_scalar' ) ); } } return $out; } /** * Turns a flow result into a response. * * @param array|WP_Error $result Flow result. * @return WP_REST_Response */ private static function respond( $result ) { if ( is_wp_error( $result ) ) { $data = (array) $result->get_error_data(); $status = isset( $data['status'] ) ? (int) $data['status'] : 400; if ( false !== strpos( $result->get_error_code(), 'too_many' ) || isset( $data['retry_after'] ) ) { $status = 429; } $response = new WP_REST_Response( array( 'ok' => false, 'code' => $result->get_error_code(), 'message' => $result->get_error_message(), 'retry_after' => isset( $data['retry_after'] ) ? (int) $data['retry_after'] : 0, ), $status ); } else { $response = new WP_REST_Response( array_merge( array( 'ok' => true ), $result ), 200 ); } $response->header( 'Cache-Control', 'no-store' ); return $response; } }