| @@ -55,12 +55,9 @@ | ||
| 55 | 55 | if ( wp_unslash( $_SERVER['REQUEST_METHOD'] ) !== 'POST' ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
| 56 | 56 | return false; |
| 57 | 57 | } |
| 58 | 58 | |
| 59 | - // Compared as a path, not as text. `sanitize_text_field()` strips percent-encoded octets | |
| 60 | - // and tags, so it would let `/flywp-magic-login%20` and friends match here while WordPress | |
| 61 | - // itself would 404 them — quietly defeating any nginx `location =`, WAF rule or allowlist | |
| 62 | - // a host puts in front of this endpoint. | |
| 59 | + // Compared as a path. Do not swap in `sanitize_text_field()`, which alters the value. | |
| 63 | 60 | $path = wp_parse_url( wp_unslash( $_SERVER['REQUEST_URI'] ), PHP_URL_PATH ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
| 64 | 61 | |
| 65 | 62 | return $path === self::PATH; |
| 66 | 63 | } |
| @@ -100,11 +97,11 @@ | ||
| 100 | 97 | if ( $token === '' ) { |
| 101 | 98 | $this->refuse( 'missing_token' ); |
| 102 | 99 | } |
| 103 | 100 | |
| 104 | - $claims = MagicLoginToken::parse( $token, flywp()->get_key(), time() ); | |
| 101 | + $claims = MagicLoginToken::parse( $token, flywp()->get_login_public_key(), time() ); | |
| 105 | 102 | |
| 106 | - if ( ! $claims ) { | |
| 103 | + if ( $claims === null ) { | |
| 107 | 104 | $this->refuse( 'invalid_token' ); |
| 108 | 105 | } |
| 109 | 106 | |
| 110 | 107 | if ( ! $this->spend_token( $claims['jti'], $claims['exp'] ) ) { |
| @@ -113,10 +110,9 @@ | ||
| 113 | 110 | |
| 114 | 111 | $user = get_user_by( 'login', $claims['sub'] ); |
| 115 | 112 | |
| 116 | 113 | if ( ! $user instanceof WP_User ) { |
| 117 | - // Fail closed. This used to fall through to the first administrator on the site, | |
| 118 | - // which turned a wrong username into an administrator session. | |
| 114 | + // Fail closed on an unknown user; never substitute another account. | |
| 119 | 115 | $this->refuse( 'unknown_user', $claims['sid'] ); |
| 120 | 116 | } |
| 121 | 117 | |
| 122 | 118 | wp_set_current_user( $user->ID, $user->user_login ); |
| @@ -138,16 +134,11 @@ | ||
| 138 | 134 | |
| 139 | 135 | /** |
| 140 | 136 | * Mark a token as spent, refusing a second use of the same one. |
| 141 | 137 | * |
| 142 | - * Written before the cookie is issued, so a replay racing the original loses. | |
| 138 | + * Written before the cookie is issued. Uses an options row rather than a transient so the | |
| 139 | + * unique index on `option_name` decides the outcome; keep it that way. | |
| 143 | 140 | * |
| 144 | - * Deliberately an options row rather than a transient. A transient lives in the object cache | |
| 145 | - * when one is installed — which FlyWP installs on both stacks — where it can be evicted under | |
| 146 | - * memory pressure or dropped by a cache flush, and the read-then-write a transient needs is a | |
| 147 | - * race in its own right. The unique index on `option_name` settles both: the insert either | |
| 148 | - * wins or it does not, and it survives a cache flush either way. | |
| 149 | - * | |
| 150 | 141 | * @param string $jti Token identifier. |
| 151 | 142 | * @param int $exp Token expiry, as a unix timestamp. |
| 152 | 143 | * |
| 153 | 144 | * @return bool False when this token has been used already, or the marker could not be stored. |
| @@ -209,10 +200,9 @@ | ||
| 209 | 200 | * @param int|null $site_id FlyWP site id, when the token got far enough to name one. |
| 210 | 201 | */ |
| 211 | 202 | do_action( 'flywp_magic_login_failed', $reason, $site_id ); |
| 212 | 203 | |
| 213 | - // Behind WP_DEBUG_LOG: this endpoint is unauthenticated, so an unconditional write here is | |
| 214 | - // a disk-fill anyone can drive. Listeners on the action above get every attempt regardless. | |
| 204 | + // Behind WP_DEBUG_LOG. Listeners on the action above get every attempt regardless. | |
| 215 | 205 | if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) { |
| 216 | 206 | error_log( sprintf( 'FlyWP magic login refused (%s) for site %s', $reason, $site_id === null ? 'unknown' : $site_id ) ); |
| 217 | 207 | } |
| 218 | 208 | |