-
Notifications
You must be signed in to change notification settings - Fork 295
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/magic link #1116
base: 15.next-cake5
Are you sure you want to change the base?
Feature/magic link #1116
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,7 +13,7 @@ jobs: | |
strategy: | ||
fail-fast: false | ||
matrix: | ||
php-version: ['8.1', '8.2', '8.3'] | ||
php-version: ['8.2', '8.3', '8.4'] | ||
db-type: [sqlite, mysql, pgsql] | ||
prefer-lowest: [''] | ||
|
||
|
@@ -59,22 +59,22 @@ jobs: | |
fi | ||
|
||
- name: Setup problem matchers for PHPUnit | ||
if: matrix.php-version == '8.1' && matrix.db-type == 'mysql' | ||
if: matrix.php-version == '8.2' && matrix.db-type == 'mysql' | ||
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" | ||
|
||
- name: Run PHPUnit | ||
run: | | ||
if [[ ${{ matrix.db-type }} == 'sqlite' ]]; then export DB_URL='sqlite:///:memory:'; fi | ||
if [[ ${{ matrix.db-type }} == 'mysql' ]]; then export DB_URL='mysql://root:[email protected]/cakephp?encoding=utf8'; fi | ||
if [[ ${{ matrix.db-type }} == 'pgsql' ]]; then export DB_URL='postgres://postgres:[email protected]/postgres'; fi | ||
if [[ ${{ matrix.php-version }} == '8.1' ]]; then | ||
if [[ ${{ matrix.php-version }} == '8.2' ]]; then | ||
export CODECOVERAGE=1 && vendor/bin/phpunit --display-deprecations --display-incomplete --display-skipped --coverage-clover=coverage.xml | ||
else | ||
vendor/bin/phpunit | ||
fi | ||
|
||
- name: Submit code coverage | ||
if: matrix.php-version == '8.1' | ||
if: matrix.php-version == '8.2' | ||
uses: codecov/codecov-action@v1 | ||
|
||
cs-stan: | ||
|
@@ -87,7 +87,7 @@ jobs: | |
- name: Setup PHP | ||
uses: shivammathur/setup-php@v2 | ||
with: | ||
php-version: '8.1' | ||
php-version: '8.2' | ||
extensions: mbstring, intl, apcu | ||
coverage: none | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
Magic Link | ||
=============================== | ||
The plugin offers an easy way to add one-click login capabilities (through a link sent to user email) | ||
|
||
|
||
Installation Requirement | ||
------------------------ | ||
There are no package requirements for using this feature. `Users.Email.required` setting must be set to true | ||
|
||
By default the feature is enabled. The default configuration is: | ||
|
||
```php | ||
'OneTimeLogin' => [ | ||
'enabled' => true, | ||
'tokenLifeTime' => 600, | ||
'DeliveryHandlers' => [ | ||
'Email' => [ | ||
'className' => \CakeDC\Users\Model\Behavior\OneTimeDelivery\EmailDelivery::class | ||
], | ||
], | ||
], | ||
``` | ||
* `tokenLifeTime`: 60 minutes by default. You can set how many seconds you want your token to be valid. | ||
* `DelveryHandlers`: Email delivery is included but it can be easily extended implementing `\CakeDC\Users\Model\Behavior\OneTimeDelivery\DeliveryInterface` (i.e SmsDelivery, PushDelivery, etc) | ||
|
||
Enabling | ||
-------- | ||
|
||
The feature is enabled by default but you can disable it application-wide and enable via Middleware (or any other way) for specific situations using: | ||
|
||
```php | ||
Configure::write('OneTimeLogin.enabled', true), | ||
``` | ||
|
||
Disabling | ||
--------- | ||
You can disable it by adding this in your config/users.php file: | ||
|
||
```php | ||
'OneTimeLogin.enabled' => false, | ||
``` | ||
|
||
How does it work | ||
---------------- | ||
When the user access the login page, there is a new button `Send me a login link`. On click, the user will be redirected to a page to enter his email address. Once it is submitted, the user will receive an email with the link to automatically login. | ||
|
||
Two-factor authentication | ||
---------------- | ||
The two-factor authentication is skipped by default for this feature since the user must actively click on a link sent to his email address. | ||
|
||
If you want to enable it by adding this in your config/users.php file: | ||
|
||
```php | ||
'Auth.Authenticators.OneTimeToken.skipTwoFactorVerify' => false, | ||
``` | ||
|
||
ReCaptcha | ||
---------------- | ||
ReCaptcha will be added automatically to the request login link form if `Users.reCaptcha.login` is enabled. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would add |
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
use Migrations\AbstractMigration; | ||
|
||
class AddLoginTokenToUsers extends AbstractMigration | ||
{ | ||
/** | ||
* Change Method. | ||
* | ||
* More information on this method is available here: | ||
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method | ||
* @return void | ||
*/ | ||
public function change(): void | ||
{ | ||
$table = $this->table('users'); | ||
$table->addColumn('login_token', 'string', [ | ||
'default' => null, | ||
'limit' => 32, | ||
'null' => true, | ||
])->addColumn('login_token_date', 'datetime', [ | ||
'default' => null, | ||
'null' => true, | ||
])->addColumn('token_send_requested', 'boolean', [ | ||
'default' => false, | ||
'null' => false, | ||
]); | ||
$table->update(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
/** | ||
* Copyright 2010 - 2019, Cake Development Corporation (https://www.cakedc.com) | ||
* | ||
* Licensed under The MIT License | ||
* Redistributions of files must retain the above copyright notice. | ||
* | ||
* @copyright Copyright 2010 - 2018, Cake Development Corporation (https://www.cakedc.com) | ||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php) | ||
*/ | ||
|
||
namespace CakeDC\Users\Controller\Traits; | ||
|
||
use Cake\Datasource\Exception\RecordNotFoundException; | ||
use CakeDC\Users\Utility\UsersUrl; | ||
|
||
/** | ||
* Covers the login, logout and social login | ||
* | ||
* @property \Cake\Http\ServerRequest $request | ||
*/ | ||
trait OneTimeTokenTrait | ||
{ | ||
/** | ||
* Request a single token login link. | ||
* | ||
* @return \Cake\Http\Response|null | ||
*/ | ||
public function requestLoginLink() | ||
{ | ||
if ($this->getRequest()->is('post')) { | ||
$email = $this->getRequest()->getData('email'); | ||
try { | ||
/** @var \CakeDC\Users\Model\Table\UsersTable $Users */ | ||
$Users = $this->getUsersTable(); | ||
/** @uses \CakeDC\Users\Model\Behavior\OneTimeLoginLinkBehavior::sendLoginLink() */ | ||
$Users->sendLoginLink($email); | ||
} catch (RecordNotFoundException $e) { | ||
$this->log( | ||
sprintf('A user is trying to get a login link for the email %s but it does not exist.', $email) | ||
); | ||
} | ||
$msg = __d( | ||
'cake_d_c/users', | ||
'If your user is registered in the system you will receive an email ' . | ||
'with a link so you can access your user area.' | ||
); | ||
$this->Flash->success($msg); | ||
$this->setRequest($this->getRequest()->withoutData('email')); | ||
|
||
return $this->redirect(UsersUrl::actionUrl('login')); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
/** | ||
* Single token login. | ||
* | ||
* @return \Cake\Http\Response|null | ||
*/ | ||
public function singleTokenLogin() | ||
{ | ||
$errorMessage = null; | ||
$token = null; | ||
if ($this->getRequest()->is('get')) { | ||
$token = $this->getRequest()->getQuery('token'); | ||
} | ||
|
||
if ($this->getRequest()->is('post') || $token) { | ||
$user = $this->Authentication->getIdentity(); | ||
$token = $this->getRequest()->getData('token', $token); | ||
if (is_array($token)) { | ||
$token = join($token); | ||
} | ||
if (!$user && !empty($token)) { | ||
$errorMessage = __d('cake_d_c/users', 'Invalid or expired token. Please request a new one.'); | ||
} | ||
} | ||
|
||
if ($errorMessage) { | ||
$this->Flash->error($errorMessage); | ||
|
||
return $this->redirect(UsersUrl::actionUrl('login')); | ||
} | ||
|
||
return $this->redirect('/'); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -41,6 +41,7 @@ trait PasswordManagementTrait | |
*/ | ||
public function changePassword($id = null) | ||
{ | ||
/** @var \CakeDC\Users\Model\Entity\User $user */ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might not be a User entity if the user has the Users table override |
||
$user = $this->getUsersTable()->newEntity([], ['validate' => false]); | ||
$user->setNew(false); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,6 +42,7 @@ public function profile($id = null) | |
try { | ||
$appContain = (array)Configure::read('Auth.Profile.contain'); | ||
$socialContain = Configure::read('Users.Social.login') ? ['SocialAccounts'] : []; | ||
/** @var \CakeDC\Users\Model\Entity\User $user */ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might not be a User entity if the user has the Users table override |
||
$user = $this->getUsersTable()->get($id, contain: array_merge($appContain, $socialContain)); | ||
$this->set('avatarPlaceholder', Configure::read('Users.Avatar.placeholder')); | ||
if ($user->id === $loggedUserId) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we'll need to keep 8.1 to max compatibility with cakephp 5