| 1 |
<?php |
| 2 |
/** |
| 3 |
* Security functionality |
| 4 |
* |
| 5 |
* @package MagicLogin |
| 6 |
*/ |
| 7 |
|
| 8 |
namespace MagicLogin\Security; |
| 9 |
|
| 10 |
use const MagicLogin\Constants\LOGIN_REQUEST_FAILSAFE_TRANSIENT_PREFIX; |
| 11 |
|
| 12 |
if ( ! defined( 'ABSPATH' ) ) { |
| 13 |
exit; // Exit if accessed directly. |
| 14 |
} |
| 15 |
|
| 16 |
/** |
| 17 |
* Default setup routine. |
| 18 |
* |
| 19 |
* @return void |
| 20 |
*/ |
| 21 |
function setup() { |
| 22 |
add_filter( 'magic_login_pre_send_login_link', __NAMESPACE__ . '\\maybe_enforce_login_request_failsafe', 1, 2 ); |
| 23 |
add_action( 'magic_login_send_login_link', __NAMESPACE__ . '\\observe_login_request_failsafe', 10, 1 ); |
| 24 |
} |
| 25 |
|
| 26 |
/** |
| 27 |
* Apply a per-user failsafe for login emails. |
| 28 |
* |
| 29 |
* @param \WP_Error|null $result Current result. |
| 30 |
* @param \WP_User|null $user User object. |
| 31 |
* |
| 32 |
* @return \WP_Error|null |
| 33 |
*/ |
| 34 |
function maybe_enforce_login_request_failsafe( $result, $user = null ) { |
| 35 |
if ( ! is_null( $result ) ) { |
| 36 |
return $result; |
| 37 |
} |
| 38 |
|
| 39 |
if ( ! is_a( $user, '\WP_User' ) ) { |
| 40 |
return $result; |
| 41 |
} |
| 42 |
|
| 43 |
$limit = defined( 'MAGIC_LOGIN_REQUEST_FAILSAFE_LIMIT' ) ? absint( MAGIC_LOGIN_REQUEST_FAILSAFE_LIMIT ) : 60; |
| 44 |
$limit = absint( apply_filters( 'magic_login_request_failsafe_limit', $limit, $user ) ); |
| 45 |
|
| 46 |
if ( $limit < 1 ) { |
| 47 |
return $result; |
| 48 |
} |
| 49 |
|
| 50 |
$minute_time = (int) floor( time() / MINUTE_IN_SECONDS ); |
| 51 |
$transient_key = LOGIN_REQUEST_FAILSAFE_TRANSIENT_PREFIX . $user->ID; |
| 52 |
$send_arr = get_site_transient( $transient_key ); |
| 53 |
$total_sent = 0; |
| 54 |
|
| 55 |
if ( ! is_array( $send_arr ) ) { |
| 56 |
$send_arr = []; |
| 57 |
} |
| 58 |
|
| 59 |
for ( $i = $minute_time; $i > $minute_time - 60; $i-- ) { |
| 60 |
if ( isset( $send_arr[ $i ] ) ) { |
| 61 |
$total_sent += absint( $send_arr[ $i ] ); |
| 62 |
} |
| 63 |
} |
| 64 |
|
| 65 |
if ( $total_sent >= $limit ) { |
| 66 |
return new \WP_Error( 'magic_login_request_failsafe_block', esc_html__( 'We have already sent several login emails recently. Please check your inbox and spam folder before requesting another link.', 'magic-login' ) ); |
| 67 |
} |
| 68 |
|
| 69 |
return $result; |
| 70 |
} |
| 71 |
|
| 72 |
/** |
| 73 |
* Observe sent login emails for the per-user failsafe. |
| 74 |
* |
| 75 |
* @param \WP_User|null $user User object. |
| 76 |
* |
| 77 |
* @return void |
| 78 |
*/ |
| 79 |
function observe_login_request_failsafe( $user = null ) { |
| 80 |
if ( ! is_a( $user, '\WP_User' ) ) { |
| 81 |
return; |
| 82 |
} |
| 83 |
|
| 84 |
$limit = defined( 'MAGIC_LOGIN_REQUEST_FAILSAFE_LIMIT' ) ? absint( MAGIC_LOGIN_REQUEST_FAILSAFE_LIMIT ) : 60; |
| 85 |
$limit = absint( apply_filters( 'magic_login_request_failsafe_limit', $limit, $user ) ); |
| 86 |
|
| 87 |
if ( $limit < 1 ) { |
| 88 |
return; |
| 89 |
} |
| 90 |
|
| 91 |
$minute_time = (int) floor( time() / MINUTE_IN_SECONDS ); |
| 92 |
$transient_key = LOGIN_REQUEST_FAILSAFE_TRANSIENT_PREFIX . $user->ID; |
| 93 |
$send_arr = get_site_transient( $transient_key ); |
| 94 |
$updated_send_arr = []; |
| 95 |
|
| 96 |
if ( ! is_array( $send_arr ) ) { |
| 97 |
$send_arr = []; |
| 98 |
} |
| 99 |
|
| 100 |
$send_arr[ $minute_time ] = isset( $send_arr[ $minute_time ] ) ? absint( $send_arr[ $minute_time ] ) + 1 : 1; |
| 101 |
|
| 102 |
for ( $i = $minute_time; $i > $minute_time - 60; $i-- ) { |
| 103 |
if ( isset( $send_arr[ $i ] ) ) { |
| 104 |
$updated_send_arr[ $i ] = absint( $send_arr[ $i ] ); |
| 105 |
} |
| 106 |
} |
| 107 |
|
| 108 |
set_site_transient( $transient_key, $updated_send_arr, HOUR_IN_SECONDS ); |
| 109 |
} |
| 110 |
|