Verified Commit cacc6a45 authored by noplanman's avatar noplanman
Browse files

Add CSS and JS and restyle profile section.

Make use of new random generator methods.
parent c60626fd
......@@ -19,6 +19,41 @@ use WP_User;
* @since 0.1.0
*/
class Wp_Otp_Admin {
/**
* Register the stylesheets for the admin area.
*
* @since 1.0.0
*
* @param string $hook Page on which this hook is called.
*/
public function enqueue_styles( $hook ) {
if ( 'profile.php' === $hook ) {
wp_enqueue_style( WP_OTP_SLUG . '-admin', plugin_dir_url( __FILE__ ) . 'css/wp-otp-admin.css' );
}
}
/**
* Register the scripts for the admin area.
*
* @since 1.0.0
*
* @param string $hook Page on which this hook is called.
*/
public function enqueue_scripts( $hook ) {
if ( 'profile.php' === $hook ) {
$handle = WP_OTP_SLUG . '-admin';
wp_enqueue_script( $handle, plugin_dir_url( __FILE__ ) . 'js/wp-otp-admin.js', [ 'jquery' ], null, true );
wp_localize_script( $handle, 'wp_otp', [
'confirm_reconfigure' =>
__( 'Are you sure you want to reconfigure WP-OTP?', 'wp-otp' ),
'confirm_new_recovery_codes' =>
__( 'Are you sure you want to regenerate your recovery codes?', 'wp-otp' ),
] );
}
}
/**
* Check and save the OTP data when saving the user profile.
*
......@@ -37,39 +72,34 @@ class Wp_Otp_Admin {
$user_meta_data = Wp_Otp_User_Meta::get_instance();
$otp = new TOTP(
$user->user_login,
$user_meta_data->get( 'secret' )
);
$secret = $otp->getSecret();
// Get the secret.
$secret = $user_meta_data->get( 'secret', $this->get_random_secret() );
$user_meta_data->set( 'secret', $secret );
$otp = new TOTP( $user->user_login, $secret );
$otp_code = trim( $_POST['wp_otp_code'] );
$otp_code = isset( $_POST['wp_otp_code'] ) ? (int) $_POST['wp_otp_code'] : 0;
if ( $otp_code && ! $user_meta_data->get( 'enabled', false ) ) {
$otp_window = (int) apply_filters( 'wp_otp_code_expiration_window', 2 );
$verification = $otp->verify( $otp_code, null, $otp_window );
if ( $verification ) {
$otp_recovery = bin2hex( random_bytes( 8 ) );
if ( $otp->verify( $otp_code, null, $otp_window ) ) {
$otp_recovery_codes = $this->get_random_recovery_codes();
$user_meta_data->set_all( [
'enabled' => true,
'recovery' => $otp_recovery,
'notice' => [
'enabled' => true,
'recovery_codes' => $otp_recovery_codes,
'notice' => [
'type' => 'success',
'messages' => [
'<strong>' . __( 'WP-OTP configured successfully!', 'wp-otp' ) . '</strong>',
__( 'If you change your phone or do not have access to the OTP Authenticator app you can use the following key as a One Time Password on your login screen and then reconfigure WP OTP. Never share this key with anyone!',
__( 'If you change your phone or do not have access to the OTP Authenticator app you can use the following codes as One Time Passwords on your login screen and then reconfigure WP-OTP.',
'wp-otp' ),
$otp_recovery,
'<br>' . __( 'Keep these codes secret!', 'wp-otp' ),
implode( '<br>', array_keys( $otp_recovery_codes ) ),
],
],
] );
} else {
Wp_Otp_User_Meta::clear();
$user_meta_data->set_all( [
'secret' => $secret,
'notice' => [
'type' => 'error',
'messages' => [
......@@ -80,7 +110,7 @@ class Wp_Otp_Admin {
] );
}
$user_meta_data->save();
$user_meta_data->set( 'secret', $secret, true );
}
}
......@@ -90,11 +120,30 @@ class Wp_Otp_Admin {
* @since 0.1.0
*/
public function admin_init() {
if ( isset( $_GET['wp-otp-delete'] ) && 'yes' === $_GET['wp-otp-delete'] ) {
if ( isset( $_GET['wp-otp-reconfigure'] ) && 'yes' === $_GET['wp-otp-reconfigure'] ) {
Wp_Otp_User_Meta::clear();
wp_redirect( get_edit_profile_url() . '#wp_otp' );
exit;
}
if ( isset( $_GET['wp-otp-new-recovery-codes'] ) && 'yes' === $_GET['wp-otp-new-recovery-codes'] ) {
$otp_recovery_codes = $this->get_random_recovery_codes();
Wp_Otp_User_Meta::get_instance()->set_all( [
'recovery_codes' => $otp_recovery_codes,
'notice' => [
'type' => 'success',
'messages' => [
'<strong>' . __( 'WP-OTP recovery codes regenerated!', 'wp-otp' ) . '</strong>',
__( 'Here are your new recovery codes.', 'wp-otp' ),
'<br>' . __( 'Keep these codes secret!', 'wp-otp' ),
implode( '<br>', array_keys( $otp_recovery_codes ) ),
],
],
], true );
wp_redirect( get_edit_profile_url() );
exit;
}
}
/**
......@@ -174,18 +223,14 @@ class Wp_Otp_Admin {
public function user_profile_render( $user ) {
$user_meta_data = Wp_Otp_User_Meta::get_instance();
// Get the secret.
$secret = $user_meta_data->get( 'secret' );
// Get and save the secret.
$secret = $user_meta_data->get( 'secret', $this->get_random_secret() );
$user_meta_data->set( 'secret', $secret, true );
$otp = new TOTP( $user->user_login, $secret );
// Issuer isn't allowed to have any semicolon.
$otp->setIssuer( str_replace( [ ':', '%3a', '%3A' ], '', get_bloginfo( 'name' ) ) );
// Check if the secret was loaded from the meta or not.
if ( null === $secret ) {
$secret = $otp->getSecret();
$user_meta_data->set( 'secret', $secret, true );
}
// Issuer isn't allowed to have any colon.
$otp->setIssuer( str_replace( [ ':', '%3a', '%3A' ], '', get_bloginfo( 'name' ) ) );
/**
* Filter for the OTP QR code provisioning URI.
......@@ -201,7 +246,7 @@ class Wp_Otp_Admin {
'https://api.qrserver.com/v1/create-qr-code/?data={PROVISIONING_URI}&qzone=2&size=300x300'
) );
$otp_enabled = $user_meta_data->get( 'enabled', false );
$otp_enabled = $user_meta_data->get( 'enabled' );
$otp_apps = [
[
......@@ -269,25 +314,38 @@ class Wp_Otp_Admin {
public function admin_notices() {
$user_meta_data = Wp_Otp_User_Meta::get_instance();
if ( ! $user_meta_data->get( 'enabled', false ) ) {
/*if ( ! $user_meta_data->get( 'enabled' ) ) {
$this->show_user_notification( [
__( '<strong>Note:</strong> You have not yet configured WP-OTP.', 'wp-otp' ),
__( '<strong>Note:</strong> You have not configured WP-OTP yet.', 'wp-otp' ),
sprintf(
'<a href="%1$s#wp_otp">%2$s</a>',
'<a href="%1$s#wp_otp" class="button">%2$s</a>',
get_edit_profile_url(),
_x( 'Configure now', 'Link text to go to WP-OTP section in user profile', 'wp-otp' )
),
] );
} elseif ( null === $user_meta_data->get( 'recovery' ) ) {
$this->show_user_notification( [
__( '<strong>Important:</strong> You have used your WP-OTP recovery hash. You must generate a new one.',
'wp-otp' ),
sprintf(
'<a href="%1$s#wp_otp">%2$s</a>',
get_edit_profile_url(),
_x( 'Configure now', 'Link text to go to WP-OTP section in user profile', 'wp-otp' )
),
], 'error' );
} else {*/
if ( $user_meta_data->get( 'enabled' ) ) {
$recovery_codes = array_filter( $user_meta_data->get( 'recovery_codes' ) );
$recovery_codes_count = count( $recovery_codes );
if ( $recovery_codes_count < 3 ) {
$this->show_user_notification( [
'<strong>' . __( 'Important', 'wp-otp' ) . '</strong>',
sprintf(
_n(
'You have %d WP-OTP recovery code left. You should generate new ones.',
'You have %d WP-OTP recovery codes left. You should generate new ones.',
$recovery_codes_count,
'wp-otp'
),
$recovery_codes_count
),
sprintf(
'<a href="%1$s" class="button">%2$s</a>',
add_query_arg( 'wp-otp-new-recovery-codes', 'yes', get_edit_profile_url() ),
_x( 'Regenerate', 'Link to regenerate the WP-OTP recovery codes', 'wp-otp' )
),
], 'error' );
}
}
if ( $notice = $user_meta_data->get( 'notice' ) ) {
......
.wp-otp-app-box {
display: inline-block;
margin: 8px 16px;
}
.wp-otp-link-reconfigure,
.wp-otp-link-new-recovery-codes {
margin: 4px !important;
}
.wp-otp-recovery-codes-box del,
.wp-otp-recovery-codes-box span {
font-weight: normal;
display: block;
margin: 4px;
}
.wp-otp-recovery-codes-box del {
color: #800;
}
(function ( $ ) {
'use strict';
$( function () {
$( '.wp-otp-link-reconfigure' ).click( function () {
return confirm( wp_otp.confirm_reconfigure );
} );
$( '.wp-otp-link-new-recovery-codes' ).click( function () {
return confirm( wp_otp.confirm_new_recovery_codes );
} );
} );
})( jQuery );
\ No newline at end of file
......@@ -7,28 +7,40 @@
?>
<a name="wp_otp"></a>
<h2>Set up WP-OTP</h2>
<h2><?php _e( 'Set up WP-OTP (WordPress One Time Password)', 'wp-otp' ); ?></h2>
<table class="form-table">
<tr>
<th scope="row">
<?php echo __( 'OTP Secret', 'wp-otp' ) . ':<br>' . $secret . '<br><br>'; ?>
<?php if ( $otp_enabled ): ?>
<?php
printf(
'<em>%1$s</em><br><a href="%2$s" onclick = "return confirm(\'%3$s\')">%4$s</a>',
'<em>%1$s</em><br><a href="%2$s" class="button button-small wp-otp-link-reconfigure">%3$s</a>',
__( 'WP-OTP has been configured successfully.', 'wp-otp' ),
add_query_arg( 'wp-otp-delete', 'yes' ),
__( 'Are you sure you want to reconfigure WP-OTP?', 'wp-otp' ),
__( 'Reconfigure?', 'wp-otp' )
add_query_arg( 'wp-otp-reconfigure', 'yes' ),
_x( 'Reconfigure', 'Link to reset and reconfigure WP-OTP secret', 'wp-otp' )
);
?>
<br><br>
<div class="wp-otp-recovery-codes-box">
<?php _e( 'Recovery codes', 'wp-otp' ); ?>:<br>
<?php
foreach ( $user_meta_data->get( 'recovery_codes' ) as $code => $unused ) {
printf( '<%1$s>%2$s</%1$s>', $unused ? 'span' : 'del', $code );
}
printf(
'<a href="%1$s" class="button button-small wp-otp-link-new-recovery-codes">%2$s</a>',
add_query_arg( 'wp-otp-new-recovery-codes', 'yes' ),
_x( 'Regenerate', 'Link to regenerate the WP-OTP recovery codes', 'wp-otp' )
);
?>
</div>
<?php else: ?>
<em><?php _e( 'To activate WP-OTP, enter the One Time Password from your authenticator app and save your profile.',
'wp-otp' ); ?></em><br><br>
'wp-otp' ); ?></em><br><br>
<label for="wp_otp_code"><?php _e( 'One Time Password', 'wp-otp' ); ?></label><br>
<input type="text" size="25" name="wp_otp_code" id="wp_otp_code"/>
<?php endif; ?>
<br><br>
<?php printf( __( 'OTP Secret:<br> %s', 'wp-otp' ), implode( ' ', str_split( $secret, 4 ) ) ); ?>
</th>
<td>
<img src="<?php echo $otp_qr_code_uri; ?>"/><br>
......@@ -40,27 +52,28 @@
</span><br><br>
<?php foreach ( $otp_apps as $otp_app ): ?>
<?php $app_name = esc_attr( $otp_app['name'] ); ?>
<strong><?php echo $otp_app['name']; ?></strong><br>
<a href="<?php echo $otp_app['uri']; ?>" target="_blank">
<img src="<?php echo $otp_app['uri_logo']; ?>"
alt="<?php echo $app_name; ?>"
title="<?php echo $app_name; ?>"
/></a>&nbsp;
<?php foreach ( $app_providers as $app_provider_key => $app_provider ): ?>
<?php
$get_it_on_text = sprintf(
esc_attr__( 'Get it on %s', 'wp-otp' ),
$app_provider['name']
);
?>
<a href="<?php echo $otp_app[ 'uri_' . $app_provider_key ]; ?>" target="_blank">
<img src="<?php echo $app_provider['uri_logo']; ?>"
alt="<?php echo $get_it_on_text; ?>"
title="<?php echo $get_it_on_text; ?>"
<span class="wp-otp-app-box">
<?php $app_name = esc_attr( $otp_app['name'] ); ?>
<strong><?php echo $otp_app['name']; ?></strong><br>
<a href="<?php echo $otp_app['uri']; ?>" target="_blank">
<img src="<?php echo $otp_app['uri_logo']; ?>"
alt="<?php echo $app_name; ?>"
title="<?php echo $app_name; ?>"
/></a>&nbsp;
<?php endforeach; ?>
<br><br>
<?php foreach ( $app_providers as $app_provider_key => $app_provider ): ?>
<?php
$get_it_on_text = sprintf(
esc_attr__( 'Get it on %s', 'wp-otp' ),
$app_provider['name']
);
?>
<a href="<?php echo $otp_app[ 'uri_' . $app_provider_key ]; ?>" target="_blank">
<img src="<?php echo $app_provider['uri_logo']; ?>"
alt="<?php echo $get_it_on_text; ?>"
title="<?php echo $get_it_on_text; ?>"
/></a>&nbsp;
<?php endforeach; ?>
</span>
<?php endforeach; ?>
</td>
</tr>
......
......@@ -108,7 +108,7 @@ class Wp_Otp_Setup {
check_admin_referer( 'bulk-plugins' );
// Important: Check if the file is the one that was registered during the uninstall hook.
if ( $file !== WP_UNINSTALL_PLUGIN ) {
if ( WP_UNINSTALL_PLUGIN !== $file ) {
return;
}
}
......
......@@ -11,7 +11,7 @@ namespace Wp_Otp;
/**
* The core plugin class.
*
* @since 0.1.0
* @since 0.1.0
*/
class Wp_Otp {
/**
......@@ -100,6 +100,9 @@ class Wp_Otp {
private function define_admin_hooks() {
$plugin_admin = new Wp_Otp_Admin();
$this->loader->add_action( 'admin_enqueue_scripts', $plugin_admin, 'enqueue_styles' );
$this->loader->add_action( 'admin_enqueue_scripts', $plugin_admin, 'enqueue_scripts' );
$this->loader->add_action( 'admin_init', $plugin_admin );
$this->loader->add_action( 'admin_notices', $plugin_admin );
......
......@@ -85,8 +85,8 @@ class Wp_Otp_Public {
$user_meta_data = Wp_Otp_User_Meta::get_instance( $user->ID );
if ( true === $user_meta_data->get( 'enabled' ) && null !== $user_meta_data->get( 'secret' ) ) {
$otp_code = $_POST['wp_otp_code'];
if ( $user_meta_data->get( 'enabled' ) && null !== $user_meta_data->get( 'secret' ) ) {
$otp_code = isset( $_POST['wp_otp_code'] ) ? $_POST['wp_otp_code'] : 0;
/**
* Filter for the OTP code expiration window.
......@@ -97,12 +97,15 @@ class Wp_Otp_Public {
*/
$otp_window = (int) apply_filters( 'wp_otp_code_expiration_window', 2 );
$otp = new TOTP( '', $user_meta_data->get( 'secret' ) );
$verification = $otp->verify( $otp_code, null, $otp_window );
$otp = new TOTP( '', $user_meta_data->get( 'secret' ) );
if ( true !== $verification ) {
if ( $otp_code === $user_meta_data->get( 'recovery' ) ) {
$user_meta_data->set( 'recovery', null, true );
// If this isn't a valid OTP code, check if it's a recovery code, else fail.
if ( ! $otp->verify( $otp_code, null, $otp_window ) ) {
$recovery_codes = $user_meta_data->get( 'recovery_codes' );
if ( array_key_exists( $otp_code, $recovery_codes ) && $recovery_codes[ $otp_code ] ) {
// Unset the recovery code that has just been used.
$recovery_codes[ $otp_code ] = false;
$user_meta_data->set( 'recovery_codes', $recovery_codes, true );
} else {
return new WP_Error( 'invalid_otp', $otp_invalid_code_text );
}
......
......@@ -7,6 +7,9 @@
* Author: Armando Lüscher
* Author URI: https://noplanman.ch
* License: GPLv3 or later
*
* @package Wp_Otp
* @since 0.1.0
*/
namespace Wp_Otp;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment