From 0dac40d3683cb229fdb8989e3c633d10bff8bc67 Mon Sep 17 00:00:00 2001 From: Ollie Read Date: Thu, 20 Feb 2014 23:49:03 +0000 Subject: [PATCH 1/3] Added the multiauth:reminders-table command. First step towards reminders working. --- .../Console/RemindersTableCommand.php | 94 +++++++++++++++++++ .../Multiauth/Console/stubs/reminders.stub | 34 +++++++ .../Multiauth/MultiauthServiceProvider.php | 7 ++ 3 files changed, 135 insertions(+) create mode 100644 src/Ollieread/Multiauth/Console/RemindersTableCommand.php create mode 100644 src/Ollieread/Multiauth/Console/stubs/reminders.stub diff --git a/src/Ollieread/Multiauth/Console/RemindersTableCommand.php b/src/Ollieread/Multiauth/Console/RemindersTableCommand.php new file mode 100644 index 0000000..25587e0 --- /dev/null +++ b/src/Ollieread/Multiauth/Console/RemindersTableCommand.php @@ -0,0 +1,94 @@ +files = $files; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $fullPath = $this->createBaseMigration(); + + $this->files->put($fullPath, $this->getMigrationStub()); + + $this->info('Migration created successfully!'); + + $this->call('dump-autoload'); + } + + /** + * Create a base migration file for the reminders. + * + * @return string + */ + protected function createBaseMigration() + { + $name = 'create_password_reminders_table'; + + $path = $this->laravel['path'].'/database/migrations'; + + return $this->laravel['migration.creator']->create($name, $path); + } + + /** + * Get the contents of the reminder migration stub. + * + * @return string + */ + protected function getMigrationStub() + { + $stub = $this->files->get(__DIR__.'/stubs/reminders.stub'); + + return str_replace('password_reminders', $this->getTable(), $stub); + } + + /** + * Get the password reminder table name. + * + * @return string + */ + protected function getTable() + { + return $this->laravel['config']->get('auth.reminder.table'); + } + +} \ No newline at end of file diff --git a/src/Ollieread/Multiauth/Console/stubs/reminders.stub b/src/Ollieread/Multiauth/Console/stubs/reminders.stub new file mode 100644 index 0000000..12d1bec --- /dev/null +++ b/src/Ollieread/Multiauth/Console/stubs/reminders.stub @@ -0,0 +1,34 @@ +string('type')->index(); + $table->string('email')->index(); + $table->string('token')->index(); + $table->timestamp('created_at'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('password_reminders'); + } + +} \ No newline at end of file diff --git a/src/Ollieread/Multiauth/MultiauthServiceProvider.php b/src/Ollieread/Multiauth/MultiauthServiceProvider.php index ee13f4d..a3fd640 100644 --- a/src/Ollieread/Multiauth/MultiauthServiceProvider.php +++ b/src/Ollieread/Multiauth/MultiauthServiceProvider.php @@ -1,6 +1,7 @@ app['command.multiauth.reminders-table'] = $this->app->share(function($app) { + return new RemindersTableCommand($app['files']); + }); + + $this->commands('command.multiauth.reminders-table'); } } From e545ab07b548d5da30d6380373ab61b97d59a561 Mon Sep 17 00:00:00 2001 From: Ollie Read Date: Fri, 21 Feb 2014 00:36:48 +0000 Subject: [PATCH 2/3] Added in almost full support for password reminders. --- src/Ollieread/Multiauth/MultiManager.php | 2 +- .../Multiauth/MultiauthServiceProvider.php | 6 - .../Reminders/DatabaseReminderRepository.php | 183 ++++++++++++++++++ .../Multiauth/Reminders/PasswordBroker.php | 136 +++++++++++++ .../Reminders/PasswordBrokerManager.php | 27 +++ .../Reminders/ReminderRepositoryInterface.php | 39 ++++ .../Reminders/ReminderServiceProvider.php | 114 +++++++++++ 7 files changed, 500 insertions(+), 7 deletions(-) create mode 100644 src/Ollieread/Multiauth/Reminders/DatabaseReminderRepository.php create mode 100644 src/Ollieread/Multiauth/Reminders/PasswordBroker.php create mode 100644 src/Ollieread/Multiauth/Reminders/PasswordBrokerManager.php create mode 100644 src/Ollieread/Multiauth/Reminders/ReminderRepositoryInterface.php create mode 100644 src/Ollieread/Multiauth/Reminders/ReminderServiceProvider.php diff --git a/src/Ollieread/Multiauth/MultiManager.php b/src/Ollieread/Multiauth/MultiManager.php index b3271a9..cb14aa8 100644 --- a/src/Ollieread/Multiauth/MultiManager.php +++ b/src/Ollieread/Multiauth/MultiManager.php @@ -17,7 +17,7 @@ public function __construct($app) { } } - public function __call($name, $arguments) { + public function __call($name, $arguments = array()) { if(array_key_exists($name, $this->providers)) { return $this->providers[$name]; } diff --git a/src/Ollieread/Multiauth/MultiauthServiceProvider.php b/src/Ollieread/Multiauth/MultiauthServiceProvider.php index a3fd640..dfc7508 100644 --- a/src/Ollieread/Multiauth/MultiauthServiceProvider.php +++ b/src/Ollieread/Multiauth/MultiauthServiceProvider.php @@ -13,12 +13,6 @@ public function register() { return new MultiManager($app); }); - - $this->app['command.multiauth.reminders-table'] = $this->app->share(function($app) { - return new RemindersTableCommand($app['files']); - }); - - $this->commands('command.multiauth.reminders-table'); } } diff --git a/src/Ollieread/Multiauth/Reminders/DatabaseReminderRepository.php b/src/Ollieread/Multiauth/Reminders/DatabaseReminderRepository.php new file mode 100644 index 0000000..7832d0c --- /dev/null +++ b/src/Ollieread/Multiauth/Reminders/DatabaseReminderRepository.php @@ -0,0 +1,183 @@ +table = $table; + $this->hashKey = $hashKey; + $this->expires = $expires * 60; + $this->connection = $connection; + } + + /** + * Create a new reminder record and token. + * + * @param \Illuminate\Auth\Reminders\RemindableInterface $user + * @return string + */ + public function create(RemindableInterface $user, $type) + { + $email = $user->getReminderEmail(); + + // We will create a new, random token for the user so that we can e-mail them + // a safe link to the password reset form. Then we will insert a record in + // the database so that we can verify the token within the actual reset. + $token = $this->createNewToken($user); + + $this->getTable()->insert($this->getPayload($email, $token, $type)); + + return $token; + } + + /** + * Build the record payload for the table. + * + * @param string $email + * @param string $token + * @return array + */ + protected function getPayload($email, $token, $type) + { + return array('type' => $type, 'email' => $email, 'token' => $token, 'created_at' => new Carbon); + } + + /** + * Determine if a reminder record exists and is valid. + * + * @param \Illuminate\Auth\Reminders\RemindableInterface $user + * @param string $token + * @return bool + */ + public function exists(RemindableInterface $user, $token, $type) + { + $email = $user->getReminderEmail(); + + $reminder = $this->getTable()->where('email', $email)->where('token', $token)->where('type', $type)->first(); + + return $reminder && ! $this->reminderExpired($reminder); + } + + /** + * Determine if the reminder has expired. + * + * @param object $reminder + * @return bool + */ + protected function reminderExpired($reminder) + { + $createdPlusHour = strtotime($reminder->created_at) + $this->expires; + + return $createdPlusHour < $this->getCurrentTime(); + } + + /** + * Get the current UNIX timestamp. + * + * @return int + */ + protected function getCurrentTime() + { + return time(); + } + + /** + * Delete a reminder record by token. + * + * @param string $token + * @return void + */ + public function delete($token, $type) + { + $this->getTable()->where('token', $token)->where('type', $type)->delete(); + } + + /** + * Delete expired reminders. + * + * @return void + */ + public function deleteExpired() + { + $expired = Carbon::now()->subSeconds($this->expires); + + $this->getTable()->where('created_at', '<', $expired)->delete(); + } + + /** + * Create a new token for the user. + * + * @param \Illuminate\Auth\Reminders\RemindableInterface $user + * @return string + */ + public function createNewToken(RemindableInterface $user) + { + $email = $user->getReminderEmail(); + + $value = str_shuffle(sha1($email.spl_object_hash($this).microtime(true))); + + return hash_hmac('sha1', $value, $this->hashKey); + } + + /** + * Begin a new database query against the table. + * + * @return \Illuminate\Database\Query\Builder + */ + protected function getTable() + { + return $this->connection->table($this->table); + } + + /** + * Get the database connection instance. + * + * @return \Illuminate\Database\Connection + */ + public function getConnection() + { + return $this->connection; + } + +} \ No newline at end of file diff --git a/src/Ollieread/Multiauth/Reminders/PasswordBroker.php b/src/Ollieread/Multiauth/Reminders/PasswordBroker.php new file mode 100644 index 0000000..407be83 --- /dev/null +++ b/src/Ollieread/Multiauth/Reminders/PasswordBroker.php @@ -0,0 +1,136 @@ +users = $users; + $this->mailer = $mailer; + $this->reminders = $reminders; + $this->reminderView = $reminderView; + $this->type = $type; + } + + /** + * Send a password reminder to a user. + * + * @param array $credentials + * @param Closure $callback + * @return string + */ + public function remind(array $credentials, Closure $callback = null) + { + // First we will check to see if we found a user at the given credentials and + // if we did not we will redirect back to this current URI with a piece of + // "flash" data in the session to indicate to the developers the errors. + $user = $this->getUser($credentials); + + if (is_null($user)) + { + return self::INVALID_USER; + } + + // Once we have the reminder token, we are ready to send a message out to the + // user with a link to reset their password. We will then redirect back to + // the current URI having nothing set in the session to indicate errors. + $token = $this->reminders->create($user, $this->type); + + $this->sendReminder($user, $token, $callback); + + return self::REMINDER_SENT; + } + + /** + * Send the password reminder e-mail. + * + * @param \Illuminate\Auth\Reminders\RemindableInterface $user + * @param string $token + * @param Closure $callback + * @return void + */ + public function sendReminder(RemindableInterface $user, $token, Closure $callback = null) + { + // We will use the reminder view that was given to the broker to display the + // password reminder e-mail. We'll pass a "token" variable into the views + // so that it may be displayed for an user to click for password reset. + $view = $this->reminderView; + $type = $this->type; + + return $this->mailer->send($view, compact('token', 'user', 'type'), function($m) use ($user, $token, $type, $callback) + { + $m->to($user->getReminderEmail()); + + if ( ! is_null($callback)) call_user_func($callback, $m, $user, $type, $token); + }); + } + + /** + * Reset the password for the given token. + * + * @param array $credentials + * @param Closure $callback + * @return mixed + */ + public function reset(array $credentials, Closure $callback) + { + // If the responses from the validate method is not a user instance, we will + // assume that it is a redirect and simply return it from this method and + // the user is properly redirected having an error message on the post. + $user = $this->validateReset($credentials); + + if ( ! $user instanceof RemindableInterface) + { + return $user; + } + + $pass = $credentials['password']; + + // Once we have called this callback, we will remove this token row from the + // table and return the response from this callback so the user gets sent + // to the destination given by the developers from the callback return. + call_user_func($callback, $user, $pass); + + $this->reminders->delete($credentials['token'], $this->type); + + return self::PASSWORD_RESET; + } + + /** + * Validate a password reset for the given credentials. + * + * @param array $credentials + * @return \Illuminate\Auth\Reminders\RemindableInterface + */ + protected function validateReset(array $credentials) + { + if (is_null($user = $this->getUser($credentials))) + { + return self::INVALID_USER; + } + + if ( ! $this->validNewPasswords($credentials)) + { + return self::INVALID_PASSWORD; + } + + if ( ! $this->reminders->exists($user, $credentials['token'], $this->type)) + { + return self::INVALID_TOKEN; + } + + return $user; + } + +} diff --git a/src/Ollieread/Multiauth/Reminders/PasswordBrokerManager.php b/src/Ollieread/Multiauth/Reminders/PasswordBrokerManager.php new file mode 100644 index 0000000..cd6392b --- /dev/null +++ b/src/Ollieread/Multiauth/Reminders/PasswordBrokerManager.php @@ -0,0 +1,27 @@ + $provider) { + $this->brokers[$type] = new PasswordBroker($type, $reminders, $provider, $mailer, $reminderView); + } + } + + public function __call($name, $arguments = array()) { + if(array_key_exists($name, $this->brokers)) { + return $this->brokers[$name]; + } + } + + + +} diff --git a/src/Ollieread/Multiauth/Reminders/ReminderRepositoryInterface.php b/src/Ollieread/Multiauth/Reminders/ReminderRepositoryInterface.php new file mode 100644 index 0000000..0fbbaaf --- /dev/null +++ b/src/Ollieread/Multiauth/Reminders/ReminderRepositoryInterface.php @@ -0,0 +1,39 @@ +registerPasswordBroker(); + + $this->registerReminderRepository(); + + $this->registerCommands(); + } + + /** + * Register the password broker instance. + * + * @return void + */ + protected function registerPasswordBroker() + { + $this->app->bindShared('auth.reminder', function($app) + { + // The reminder repository is responsible for storing the user e-mail addresses + // and password reset tokens. It will be used to verify the tokens are valid + // for the given e-mail addresses. We will resolve an implementation here. + $reminders = $app['auth.reminder.repository']; + + $providers = array(); + + foreach($app['config']['auth.multi'] as $type => $config) { + $providers[$type] = $app['auth']->$type()->driver()->getProvider(); + } + + $view = $app['config']['auth.reminder.email']; + + // The password broker uses the reminder repository to validate tokens and send + // reminder e-mails, as well as validating that password reset process as an + // aggregate service of sorts providing a convenient interface for resets. + return new PasswordBrokerManager( + + $reminders, $app['mailer'], $view, $providers + + ); + }); + } + + /** + * Register the reminder repository implementation. + * + * @return void + */ + protected function registerReminderRepository() + { + $this->app->bindShared('auth.reminder.repository', function($app) + { + $connection = $app['db']->connection(); + + // The database reminder repository is an implementation of the reminder repo + // interface, and is responsible for the actual storing of auth tokens and + // their e-mail addresses. We will inject this table and hash key to it. + $table = $app['config']['auth.reminder.table']; + + $key = $app['config']['app.key']; + + $expire = $app['config']->get('auth.reminder.expire', 60); + + return new DbRepository($connection, $table, $key, $expire); + }); + } + + /** + * Register the multiauth related console commands. + * + * @return void + */ + protected function registerCommands() + { + $this->app->bindShared('command.multiauth.reminders', function($app) + { + return new RemindersTableCommand($app['files']); + }); + + $this->commands( + 'command.multiauth.reminders' + ); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('auth.reminder'); + } + +} From 347bcc1d57e3f92e07eccd067c896fd094f0aad7 Mon Sep 17 00:00:00 2001 From: Ollie Read Date: Fri, 21 Feb 2014 00:55:30 +0000 Subject: [PATCH 3/3] Updated to reflect reminders changes. --- README.md | 51 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 109c342..eec2bb2 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Firstly you want to include this package in your composer.json file. Next you open up app/config/app.php and replace the AuthServiceProvider with - Ollieread\Multiauth\MultiauthServiceProvider + "Ollieread\Multiauth\MultiauthServiceProvider" Configuration is pretty easy too, take app/config/auth.php with its default values: @@ -76,6 +76,51 @@ Now remove the first three options and replace as follows: ); +## Reminders ## + +If you wish to use reminders, you will need to replace ReminderServiceProvider in you +app/config/app.php file with the following. + + Ollieread\Multiauth\Reminders\ReminderServiceProvider + +To generate the reminders table you will need to run the following command. + + php artisan multiauth:reminders-table + +The `reminders-controller` command has been removed, as it wouldn't work with the +way this package handles authentication. I do plan to look into this in the future. + +The concept is the same as the default Auth reminders, except you access everything +the same way you do using the rest of this package, in that prefix methods with the +authentication type. + +To send a reminder you would do the following. + + Password::account()->remind(Input::only('email'), function($message) { + $message->subject('Password reminder'); + }); + +And to reset a password you would do the following. + + Password::account()->reset($credentials, function($user, $password) { + $user->password = Hash::make($password); + $user->save(); + }); + +For simple identification of which token belongs to which user, as it's perfectly feasible +that we could have two different users, of different types, with the same token, I've modified my reminder +email to have a type attribute. + + To reset your password, complete this form: {{ URL::to('password/reset', array($type, $token)) }}. + +This generates a URL like the following. + + http://laravel.ollieread.com/password/reset/account/27eb8fe5fe666b3b8d0521156bbf53266dbca572 + +Which matches the following route. + + Route::any('/password/reset/{type}/{token}', 'Controller@method'); + ## Usage ## @@ -113,10 +158,6 @@ And so on and so forth. There we go, done! Enjoy yourselves. -## Known Bugs ## - -Reminders don't currently work. - ### License This package inherits the licensing of its parent framework, Laravel, and as such is open-sourced