', 'Reply-To: support@wordcamp.org', // todo update address when new one is created ); $result = wp_mail( $to, $subject, $message, $headers ); /** * Broadcast the results of an attempt to send an email. * * @param string $to * @param string $subject * @param string $message * @param array $headers * @param bool $result * @param int $pledge_id */ do_action( FiveForTheFuture\PREFIX . '_email_result', $to, $subject, $message, $headers, $result, $pledge_id ); return $result; } /** * Generate an action URL with a secure, unique authentication token. * * @param int $pledge_id * @param string $action * @param int $action_page_id The ID of the page that the user will be taken back to, in order to process their * confirmation request. * @param bool $use_once Whether or not the token should be deleted after the first use. Only pass `false` * when the action requires several steps in a flow, rather than a single step. For * instance, be able to 1) view a private pledge; 2) make changes and save them; and * 3) reload the private pledge with the new changes displayed. * * @return string */ function get_authentication_url( $pledge_id, $action, $action_page_id, $use_once = true ) { $auth_token = array( /* * This will create a CSPRN and is similar to how `get_password_reset_key()` and * `generate_recovery_mode_token()` work. */ 'value' => wp_generate_password( TOKEN_LENGTH, false ), // todo Ideally should encrypt at rest, see https://core.trac.wordpress.org/ticket/24783. 'expiration' => time() + ( 2 * HOUR_IN_SECONDS ), 'use_once' => $use_once, ); /* * Tying the token to a specific pledge is important for security, otherwise companies could get a valid token * for their pledge, and use it to edit other company's pledges. * * Similarly, tying it to specific actions is also important, to protect against CSRF attacks. * * This function intentionally requires the caller to pass in a pledge ID and action, so that it can guarantee * that each token will be unique across pledges and actions. */ update_post_meta( $pledge_id, TOKEN_PREFIX . $action, $auth_token ); $auth_url = add_query_arg( array( 'action' => $action, 'pledge_id' => $pledge_id, 'auth_token' => $auth_token['value'], ), get_permalink( $action_page_id ) ); // todo include a "this lnk will expire in 10 hours and after its used once" message too? // probably, but what's the best way to do that DRYly? return $auth_url; } /** * Verify whether or not a given authentication token is valid. * * These tokens are more secure than WordPress' imitation nonces because they cannot be reused[1], and expire * in a shorter timeframe. Like WP nonces, though, they must be tied to a specific action and post object in order * to prevent misuse. * * [1] In some cases, tokens can be reused, when that is explicitly required for their flow. For an example, see * the documentation in `get_authentication_url()`. * * @param int $pledge_id * @param string $action * @param string $unverified_token * * @return bool */ function is_valid_authentication_token( $pledge_id, $action, $unverified_token ) { $verified = false; $valid_token = get_post_meta( $pledge_id, TOKEN_PREFIX . $action, true ); /* * Later on we'll compare the value to user input, and the user could input null/false/etc, so let's guarantee * that the thing we're comparing against is really what we expect it to be. */ if ( ! is_array( $valid_token ) || ! array_key_exists( 'value', $valid_token ) || ! array_key_exists( 'expiration', $valid_token ) ) { return false; } if ( ! is_string( $valid_token['value'] ) || TOKEN_LENGTH !== strlen( $valid_token['value'] ) ) { return false; } if ( ! is_string( $unverified_token ) || TOKEN_LENGTH !== strlen( $unverified_token ) ) { return false; } if ( $valid_token && $valid_token['expiration'] > time() && hash_equals( $valid_token['value'], $unverified_token ) ) { $verified = true; // Tokens should not be reusable -- to increase security -- unless explicitly required to fulfill their purpose. if ( $valid_token['use_once'] !== false ) { delete_post_meta( $pledge_id, TOKEN_PREFIX . $action ); } } return $verified; }