Skip to content

Commit

Permalink
Merge pull request #8 from deliciousbrains/4-single-use
Browse files Browse the repository at this point in the history
  • Loading branch information
rosswintle authored Nov 1, 2021
2 parents ea15d64 + e77e7ca commit dd83996
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 22 deletions.
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ It needs to be running PHP 5.3 or higher.

It requires the [deliciousbrains/wp-migration](https://github.com/deliciousbrains/wp-migrations) package and so the site will need to be set up to run `wp dbi migrate` as a last stage build step in your deployment process.

You should also run `wp dbi migrate` after updating the package to make sure you have up to date database tables.

It automatically purges expired keys from the database daily, and there are WP-CLI commands to:

1. Manually purge expired keys
Expand All @@ -31,16 +33,20 @@ There are two parameters you can pass when bootstrapping the package:

To generate a URL that will automatically login a user and land them at a specific URL use this function:

`dbi_get_auto_login_url( $destination_url, $user_id, $query_parms );`
`dbi_get_auto_login_url( $destination_url, $user_id, [$query_params], [$expiry], [$one_time] );`

The URL will expire in 120 days. However, you can pass the number of seconds the URL will be valid for as the fourth argument, e.g valid for 1 day:

`dbi_get_auto_login_url( $destination_url, $user_id, $query_parms, 86400 );`
`dbi_get_auto_login_url( $destination_url, $user_id, $query_params, 86400 );`

You can also specify your own global default for expiry when bootstrapping the package as explained in the "Installation" section above. Use:

`\DeliciousBrains\WPAutoLogin\AutoLogin::instance( 'dbi', <expiry_in_seconds> );`

There is also an option to generate links that can only be used once:

`dbi_get_auto_login_url( $destination_url, $user_id, $query_params, null, true );`

## WP-CLI

There are two WP-CLI commands.
Expand Down Expand Up @@ -74,3 +80,7 @@ Example:
`wp dbi auto_login_url 12345 https://example.com/dashboard --expiry=21600`

Will generate a link that logs in the user with ID 12345 and takes them to https://example.com/dashboard. The link will be valid for 6 hours.

You can add `--one-time` to generate a single-use link:

`wp dbi auto_login_url 12345 https://example.com/dashboard --one-time`
6 changes: 3 additions & 3 deletions functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
}

if ( ! function_exists( 'dbi_get_auto_login_url' ) ) {
function dbi_get_auto_login_url( $url, $user_id, $args = array(), $expires_in = null ) {
function dbi_get_auto_login_url( $url, $user_id, $args = array(), $expires_in = null, $one_time = false ) {
$autologin = \DeliciousBrains\WPAutoLogin\AutoLogin::instance();

return $autologin->create_url( $url, $user_id, $args, $expires_in );
return $autologin->create_url( $url, $user_id, $args, $expires_in, $one_time );
}
}
}
18 changes: 18 additions & 0 deletions migrations/2021_09_05_add_single_use_to_keys_table.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php


namespace DeliciousBrains\WPMigrations\Database\Migrations;

use DeliciousBrains\WPMigrations\Database\AbstractMigration;

class AddSingleUseToKeysTable extends AbstractMigration {

public function run() {
global $wpdb;

$wpdb->query( "
ALTER TABLE `{$wpdb->prefix}dbrns_auto_login_keys`
ADD COLUMN `one_time` tinyint NOT NULL DEFAULT 0;
" );
}
}
33 changes: 19 additions & 14 deletions src/AutoLogin.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class AutoLogin {
* @return AutoLogin Instance
*/
public static function instance( $command_name = 'dbi', $expires = 10368000 ) {
if ( ! isset( self::$instance ) && ! ( self::$instance instanceof AutoLogin ) ) {
if ( ! isset( self::$instance ) || ! ( self::$instance instanceof AutoLogin ) ) {
self::$instance = new AutoLogin();
self::$instance->init( $command_name, $expires );
}
Expand Down Expand Up @@ -121,7 +121,9 @@ public function handle_auto_login() {
return;
}

$user_id_for_key = $this->get_user_id_for_key( $login_key );
$key = AutoLoginKey::get_by_key( $login_key );

$user_id_for_key = $this->get_user_id_for_key( $key );

if ( $user_id_for_key === false || $user_id_for_key != $user->ID ) {
do_action( 'wp_login_failed', $user->user_login );
Expand All @@ -132,6 +134,8 @@ public function handle_auto_login() {
wp_set_auth_cookie( $user->ID );
do_action( 'wp_login', $user->user_login, $user );

$key->maybe_delete_one_time_key();

$redirect = remove_query_arg( [ 'login_key', 'user_id' ] );
wp_redirect( $redirect );
exit;
Expand All @@ -142,9 +146,7 @@ public function handle_auto_login() {
*
* @return bool|int
*/
public function get_user_id_for_key( $key_to_find ) {
$key = AutoLoginKey::get_by_key( $key_to_find );

public function get_user_id_for_key( $key ) {
if ( ! $key ) {
return false;
}
Expand All @@ -157,12 +159,13 @@ public function get_user_id_for_key( $key_to_find ) {
}

/**
* @param int $user_id
* @param null|int $expires_in Seconds
* @param int $user_id
* @param null|int $expires_in Seconds
* @param null|boolean $one_time
*
* @return bool|string
*/
public function create_key( $user_id, $expires_in = null ) {
public function create_key( $user_id, $expires_in = null, $one_time = false ) {
do {
$key = wp_generate_password( 40, false );
$already_exists = AutoLoginKey::get_by_key( $key );
Expand All @@ -171,6 +174,7 @@ public function create_key( $user_id, $expires_in = null ) {
$loginkey = new AutoLoginKey();
$loginkey->login_key = $key;
$loginkey->user_id = $user_id;
$loginkey->one_time = $one_time;
if ( $expires_in ) {
$loginkey->expires = gmdate( 'Y-m-d H:i:s', time() + $expires_in );
} else {
Expand All @@ -197,15 +201,16 @@ public function remove_expired_keys() {
}

/**
* @param string $url
* @param int $user_id
* @param array $args
* @param null|int $expires_in Seconds
* @param string $url
* @param int $user_id
* @param array $args
* @param null|int $expires_in Seconds
* @param null|boolean $one_time
*
* @return string
*/
public function create_url( $url, $user_id, $args = [], $expires_in = null ) {
$login_key = $this->create_key( $user_id, $expires_in );
public function create_url( $url, $user_id, $args = [], $expires_in = null, $one_time = false ) {
$login_key = $this->create_key( $user_id, $expires_in, $one_time );

$args = array_merge( [
'login_key' => $login_key,
Expand Down
12 changes: 9 additions & 3 deletions src/CLI/Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ class Command extends \WP_CLI_Command {
* ---
* default: 172800
* ---
* [--one-time]
* : Make the login key work only once
*
* @param array $args
* @param array $assoc_args
Expand All @@ -41,13 +44,16 @@ public function auto_login_url( $args, $assoc_args ) {
return \WP_CLI::warning( 'User not found' );
}

// Validate expiry
// Validate one-time option.
$one_time = isset( $assoc_args['one-time'] ) ? (bool) $assoc_args['one-time'] : false;

// Validate expiry. Note that WP-CLI always provides a default so we don't need an isset check.
if ( ! is_numeric( $assoc_args['expiry'] ) ) {
\WP_CLI::error( 'Please specify a numeric value for the expiry.' );
}

$url = empty( $args[1] ) ? home_url() : $args[1];
$key_url = AutoLogin::instance()->create_url( $url, $user->ID, array(), $assoc_args['expiry'] );
$url = empty( $args[1] ) ? home_url() : $args[1];
$key_url = AutoLogin::instance()->create_url( $url, $user->ID, array(), $assoc_args['expiry'], $one_time );

return \WP_CLI::success( 'Auto-login URL generated: ' . $key_url );
}
Expand Down
31 changes: 31 additions & 0 deletions src/Model/AutoLoginKey.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ class AutoLoginKey {
*/
public $expires;

/**
* Flag to indicate that key can only be used once
*
* @var boolean
*/
public $one_time;

/**
* Constructor
*
Expand All @@ -56,11 +63,19 @@ public function __construct( $attributes = array() ) {
if ( ! isset( $attributes['created'] ) ) {
$this->created = gmdate( 'Y-m-d H:i:s', time() );
}

if ( ! isset( $attributes['one_time'] ) ) {
$this->one_time = false;
} else {
$this->one_time = (bool) $attributes['one_time'];
}
}

/**
* Fetches a key object for the specified key.
*
* If the key is one-time then it will be deleted here too.
*
* @param string $key The key to fetch a record for.
* @return AutoLoginKey|null The key object to return, or null if not found.
*/
Expand All @@ -81,6 +96,21 @@ public static function get_by_key( $key ) {
return new self( $row );
}

/**
* Check if the key is one-time and to delete it from the database if it is.
*
* @return int|bool The number of rows deleted, or false if an error occurred.
* Note that both false and 0 can be returned so be careful with
* comparisons.
*/
public function maybe_delete_one_time_key() {
global $wpdb;

$table = self::$table;

return $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->prefix{$table} WHERE login_key = %s AND one_time = 1", $this->login_key ) );
}

/**
* Static method to delete legacy keys from the database.
* These keys will have an expiry of '0000-00-00 00:00:00', and will need to be
Expand Down Expand Up @@ -130,6 +160,7 @@ public function save() {
'user_id' => $this->user_id,
'expires' => $this->expires,
'created' => $this->created,
'one_time' => (int) $this->one_time,
);

return $wpdb->insert( $wpdb->prefix . self::$table, $data );
Expand Down

0 comments on commit dd83996

Please sign in to comment.