diff --git a/framework/core/composer.json b/framework/core/composer.json index ff06ace99..151719c8e 100644 --- a/framework/core/composer.json +++ b/framework/core/composer.json @@ -42,7 +42,6 @@ "illuminate/view": "5.5.*", "intervention/image": "^2.3.0", "league/flysystem": "^1.0.11", - "league/oauth2-client": "~1.0", "matthiasmullie/minify": "^1.3", "middlewares/base-path": "^1.1", "middlewares/base-path-router": "^0.2.1", diff --git a/framework/core/js/src/forum/ForumApplication.js b/framework/core/js/src/forum/ForumApplication.js index 2e52abb1d..4a1439ead 100644 --- a/framework/core/js/src/forum/ForumApplication.js +++ b/framework/core/js/src/forum/ForumApplication.js @@ -148,17 +148,16 @@ export default class ForumApplication extends Application { * with the provided details. * * @param {Object} payload A dictionary of props to pass into the sign up - * modal. A truthy `authenticated` prop indicates that the user has logged + * modal. A truthy `loggedIn` prop indicates that the user has logged * in, and thus the page is reloaded. * @public */ authenticationComplete(payload) { - if (payload.authenticated) { + if (payload.loggedIn) { window.location.reload(); } else { const modal = new SignUpModal(payload); this.modal.show(modal); - modal.$('[name=password]').focus(); } } } diff --git a/framework/core/js/src/forum/components/SignUpModal.js b/framework/core/js/src/forum/components/SignUpModal.js index b651e29aa..94ac1e162 100644 --- a/framework/core/js/src/forum/components/SignUpModal.js +++ b/framework/core/js/src/forum/components/SignUpModal.js @@ -42,7 +42,7 @@ export default class SignUpModal extends Modal { } className() { - return 'Modal--small SignUpModal' + (this.welcomeUser ? ' SignUpModal--success' : ''); + return 'Modal--small SignUpModal'; } title() { @@ -61,7 +61,7 @@ export default class SignUpModal extends Modal { } isProvided(field) { - return this.props.identificationFields && this.props.identificationFields.indexOf(field) !== -1; + return this.props.provided && this.props.provided.indexOf(field) !== -1; } body() { @@ -179,10 +179,6 @@ export default class SignUpModal extends Modal { data.password = this.password(); } - if (this.props.avatarUrl) { - data.avatarUrl = this.props.avatarUrl; - } - return data; } } diff --git a/framework/core/migrations/2018_09_22_004100_change_registration_tokens_columns.php b/framework/core/migrations/2018_09_22_004100_change_registration_tokens_columns.php new file mode 100644 index 000000000..526899c84 --- /dev/null +++ b/framework/core/migrations/2018_09_22_004100_change_registration_tokens_columns.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Database\Schema\Builder; + +return [ + 'up' => function (Builder $schema) { + $schema->table('registration_tokens', function (Blueprint $table) { + $table->string('provider'); + $table->string('identifier'); + $table->text('user_attributes')->nullable(); + + $table->text('payload')->nullable()->change(); + }); + }, + + 'down' => function (Builder $schema) { + $schema->table('auth_tokens', function (Blueprint $table) { + $table->dropColumn('provider', 'identifier', 'user_attributes'); + + $table->string('payload', 150)->change(); + }); + } +]; diff --git a/framework/core/migrations/2018_09_22_004200_create_login_providers_table.php b/framework/core/migrations/2018_09_22_004200_create_login_providers_table.php new file mode 100644 index 000000000..3d395830e --- /dev/null +++ b/framework/core/migrations/2018_09_22_004200_create_login_providers_table.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Flarum\Database\Migration; +use Illuminate\Database\Schema\Blueprint; + +return Migration::createTable( + 'login_providers', + function (Blueprint $table) { + $table->increments('id'); + $table->unsignedInteger('user_id'); + $table->string('provider', 100); + $table->string('identifier', 100); + $table->dateTime('created_at')->nullable(); + $table->dateTime('last_login_at')->nullable(); + + $table->unique(['provider', 'identifier']); + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + } +); diff --git a/framework/core/src/Forum/Auth/Registration.php b/framework/core/src/Forum/Auth/Registration.php new file mode 100644 index 000000000..46ac0d6ad --- /dev/null +++ b/framework/core/src/Forum/Auth/Registration.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Forum\Auth; + +class Registration +{ + /** + * @var array + */ + protected $provided = []; + + /** + * @var array + */ + protected $suggested = []; + + /** + * @var mixed + */ + protected $payload; + + /** + * @return array + */ + public function getProvided(): array + { + return $this->provided; + } + + /** + * @return array + */ + public function getSuggested(): array + { + return $this->suggested; + } + + /** + * @return mixed + */ + public function getPayload() + { + return $this->payload; + } + + /** + * @param string $key + * @param mixed $value + * @return $this + */ + public function provide(string $key, $value): self + { + $this->provided[$key] = $value; + + return $this; + } + + /** + * @param string $email + * @return $this + */ + public function provideTrustedEmail(string $email): self + { + return $this->provide('email', $email); + } + + /** + * @param string $url + * @return $this + */ + public function provideAvatar(string $url): self + { + return $this->provide('avatar_url', $url); + } + + /** + * @param string $key + * @param mixed $value + * @return $this + */ + public function suggest(string $key, $value): self + { + $this->suggested[$key] = $value; + + return $this; + } + + /** + * @param string $username + * @return $this + */ + public function suggestUsername(string $username): self + { + $username = preg_replace('/[^a-z0-9-_]/i', '', $username); + + return $this->suggest('username', $username); + } + + /** + * @param string $email + * @return $this + */ + public function suggestEmail(string $email): self + { + return $this->suggest('email', $email); + } + + /** + * @param mixed $payload + * @return $this + */ + public function setPayload($payload): self + { + $this->payload = $payload; + + return $this; + } +} diff --git a/framework/core/src/Forum/Auth/ResponseFactory.php b/framework/core/src/Forum/Auth/ResponseFactory.php new file mode 100644 index 000000000..37eb5e9cc --- /dev/null +++ b/framework/core/src/Forum/Auth/ResponseFactory.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Forum\Auth; + +use Flarum\Http\Rememberer; +use Flarum\User\LoginProvider; +use Flarum\User\RegistrationToken; +use Flarum\User\User; +use Psr\Http\Message\ResponseInterface; +use Zend\Diactoros\Response\HtmlResponse; + +class ResponseFactory +{ + /** + * @var Rememberer + */ + protected $rememberer; + + /** + * @param Rememberer $rememberer + */ + public function __construct(Rememberer $rememberer) + { + $this->rememberer = $rememberer; + } + + public function make(string $provider, string $identifier, callable $configureRegistration): ResponseInterface + { + if ($user = LoginProvider::logIn($provider, $identifier)) { + return $this->makeLoggedInResponse($user); + } + + $configureRegistration($registration = new Registration); + + $provided = $registration->getProvided(); + + if (! empty($provided['email']) && $user = User::where(array_only($provided, 'email'))->first()) { + $user->loginProviders()->create(compact('provider', 'identifier')); + + return $this->makeLoggedInResponse($user); + } + + $token = RegistrationToken::generate($provider, $identifier, $provided, $registration->getPayload()); + $token->save(); + + return $this->makeResponse(array_merge( + $provided, + $registration->getSuggested(), + [ + 'token' => $token->id, + 'provided' => array_keys($provided) + ] + )); + } + + private function makeResponse(array $payload): HtmlResponse + { + $content = sprintf( + '', + json_encode($payload) + ); + + return new HtmlResponse($content); + } + + private function makeLoggedInResponse(User $user) + { + $response = $this->makeResponse(['loggedIn' => true]); + + return $this->rememberer->rememberUser($response, $user->id); + } +} diff --git a/framework/core/src/Forum/AuthenticationResponseFactory.php b/framework/core/src/Forum/AuthenticationResponseFactory.php deleted file mode 100644 index 6a17c8c2d..000000000 --- a/framework/core/src/Forum/AuthenticationResponseFactory.php +++ /dev/null @@ -1,118 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Forum; - -use Flarum\Http\Rememberer; -use Flarum\Http\SessionAuthenticator; -use Flarum\User\AuthToken; -use Flarum\User\User; -use Psr\Http\Message\ServerRequestInterface as Request; -use Zend\Diactoros\Response\HtmlResponse; - -class AuthenticationResponseFactory -{ - /** - * @var SessionAuthenticator - */ - protected $authenticator; - - /** - * @var Rememberer - */ - protected $rememberer; - - /** - * AuthenticationResponseFactory constructor. - * @param SessionAuthenticator $authenticator - * @param Rememberer $rememberer - */ - public function __construct(SessionAuthenticator $authenticator, Rememberer $rememberer) - { - $this->authenticator = $authenticator; - $this->rememberer = $rememberer; - } - - public function make(Request $request, array $identification, array $suggestions = []) - { - if (isset($suggestions['username'])) { - $suggestions['username'] = $this->sanitizeUsername($suggestions['username']); - } - - $user = User::where($identification)->first(); - - $payload = $this->getPayload($identification, $suggestions, $user); - - $response = $this->getResponse($payload); - - if ($user) { - $session = $request->getAttribute('session'); - $this->authenticator->logIn($session, $user->id); - - $response = $this->rememberer->rememberUser($response, $user->id); - } - - return $response; - } - - /** - * @param string $username - * @return string - */ - private function sanitizeUsername($username) - { - return preg_replace('/[^a-z0-9-_]/i', '', $username); - } - - /** - * @param array $payload - * @return HtmlResponse - */ - private function getResponse(array $payload) - { - $content = sprintf( - '', - json_encode($payload) - ); - - return new HtmlResponse($content); - } - - /** - * @param array $identification - * @param array $suggestions - * @param User|null $user - * @return array - */ - private function getPayload(array $identification, array $suggestions, User $user = null) - { - // If a user with these attributes already exists, then we will log them - // in by generating an access token. Otherwise, we will generate a - // unique token for these attributes and add it to the response, along - // with the suggested account information. - if ($user) { - $payload = ['authenticated' => true]; - } else { - $token = AuthToken::generate($identification); - $token->save(); - - $payload = array_merge( - $identification, - $suggestions, - ['token' => $token->id], - // List of the fields that can't be edited during sign up - ['identificationFields' => array_keys($identification)] - ); - } - - return $payload; - } -} diff --git a/framework/core/src/Forum/Controller/AbstractOAuth2Controller.php b/framework/core/src/Forum/Controller/AbstractOAuth2Controller.php deleted file mode 100644 index bd6522f64..000000000 --- a/framework/core/src/Forum/Controller/AbstractOAuth2Controller.php +++ /dev/null @@ -1,107 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Forum\Controller; - -use Flarum\Forum\AuthenticationResponseFactory; -use League\OAuth2\Client\Provider\ResourceOwnerInterface; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface as Request; -use Psr\Http\Server\RequestHandlerInterface; -use Zend\Diactoros\Response\RedirectResponse; - -abstract class AbstractOAuth2Controller implements RequestHandlerInterface -{ - /** - * @var AuthenticationResponseFactory - */ - protected $authResponse; - - /** - * @var \League\OAuth2\Client\Provider\AbstractProvider - */ - protected $provider; - - /** - * The access token, once obtained. - * - * @var string - */ - protected $token; - - /** - * @param AuthenticationResponseFactory $authResponse - */ - public function __construct(AuthenticationResponseFactory $authResponse) - { - $this->authResponse = $authResponse; - } - - /** - * @param Request $request - * @return ResponseInterface - */ - public function handle(Request $request): ResponseInterface - { - $redirectUri = (string) $request->getAttribute('originalUri', $request->getUri())->withQuery(''); - - $this->provider = $this->getProvider($redirectUri); - - $session = $request->getAttribute('session'); - - $queryParams = $request->getQueryParams(); - $code = array_get($queryParams, 'code'); - $state = array_get($queryParams, 'state'); - - if (! $code) { - $authUrl = $this->provider->getAuthorizationUrl($this->getAuthorizationUrlOptions()); - $session->put('oauth2state', $this->provider->getState()); - - return new RedirectResponse($authUrl.'&display=popup'); - } elseif (! $state || $state !== $session->get('oauth2state')) { - $session->remove('oauth2state'); - echo 'Invalid state. Please close the window and try again.'; - exit; - } - - $this->token = $this->provider->getAccessToken('authorization_code', compact('code')); - - $owner = $this->provider->getResourceOwner($this->token); - - $identification = $this->getIdentification($owner); - $suggestions = $this->getSuggestions($owner); - - return $this->authResponse->make($request, $identification, $suggestions); - } - - /** - * @param string $redirectUri - * @return \League\OAuth2\Client\Provider\AbstractProvider - */ - abstract protected function getProvider($redirectUri); - - /** - * @return array - */ - abstract protected function getAuthorizationUrlOptions(); - - /** - * @param ResourceOwnerInterface $resourceOwner - * @return array - */ - abstract protected function getIdentification(ResourceOwnerInterface $resourceOwner); - - /** - * @param ResourceOwnerInterface $resourceOwner - * @return array - */ - abstract protected function getSuggestions(ResourceOwnerInterface $resourceOwner); -} diff --git a/framework/core/src/Http/Middleware/CollectGarbage.php b/framework/core/src/Http/Middleware/CollectGarbage.php index 9b0da016c..3160a6aca 100644 --- a/framework/core/src/Http/Middleware/CollectGarbage.php +++ b/framework/core/src/Http/Middleware/CollectGarbage.php @@ -13,9 +13,9 @@ namespace Flarum\Http\Middleware; use Carbon\Carbon; use Flarum\Http\AccessToken; -use Flarum\User\AuthToken; use Flarum\User\EmailToken; use Flarum\User\PasswordToken; +use Flarum\User\RegistrationToken; use Illuminate\Contracts\Config\Repository as ConfigRepository; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; @@ -64,7 +64,7 @@ class CollectGarbage implements Middleware EmailToken::where('created_at', '<=', $earliestToKeep)->delete(); PasswordToken::where('created_at', '<=', $earliestToKeep)->delete(); - AuthToken::where('created_at', '<=', $earliestToKeep)->delete(); + RegistrationToken::where('created_at', '<=', $earliestToKeep)->delete(); $this->sessionHandler->gc($this->getSessionLifetimeInSeconds()); } diff --git a/framework/core/src/User/Command/EditUserHandler.php b/framework/core/src/User/Command/EditUserHandler.php index 6d9d2485d..33261ca60 100644 --- a/framework/core/src/User/Command/EditUserHandler.php +++ b/framework/core/src/User/Command/EditUserHandler.php @@ -11,19 +11,15 @@ namespace Flarum\User\Command; -use Exception; use Flarum\Foundation\DispatchEventsTrait; use Flarum\User\AssertPermissionTrait; -use Flarum\User\AvatarUploader; use Flarum\User\Event\GroupsChanged; use Flarum\User\Event\Saving; use Flarum\User\User; use Flarum\User\UserRepository; use Flarum\User\UserValidator; use Illuminate\Contracts\Events\Dispatcher; -use Illuminate\Contracts\Validation\Factory; use Illuminate\Validation\ValidationException; -use Intervention\Image\ImageManager; class EditUserHandler { @@ -40,36 +36,23 @@ class EditUserHandler */ protected $validator; - /** - * @var AvatarUploader - */ - protected $avatarUploader; - - /** - * @var Factory - */ - private $validatorFactory; - /** * @param Dispatcher $events * @param \Flarum\User\UserRepository $users * @param UserValidator $validator - * @param AvatarUploader $avatarUploader - * @param Factory $validatorFactory */ - public function __construct(Dispatcher $events, UserRepository $users, UserValidator $validator, AvatarUploader $avatarUploader, Factory $validatorFactory) + public function __construct(Dispatcher $events, UserRepository $users, UserValidator $validator) { $this->events = $events; $this->users = $users; $this->validator = $validator; - $this->avatarUploader = $avatarUploader; - $this->validatorFactory = $validatorFactory; } /** * @param EditUser $command * @return User * @throws \Flarum\User\Exception\PermissionDeniedException + * @throws ValidationException */ public function handle(EditUser $command) { @@ -146,28 +129,6 @@ class EditUserHandler }); } - if ($avatarUrl = array_get($attributes, 'avatarUrl')) { - $this->assertPermission($canEdit); - - $validation = $this->validatorFactory->make(compact('avatarUrl'), ['avatarUrl' => 'url']); - - if ($validation->fails()) { - throw new ValidationException($validation); - } - - try { - $image = (new ImageManager)->make($avatarUrl); - - $this->avatarUploader->upload($user, $image); - } catch (Exception $e) { - // - } - } elseif (array_key_exists('avatarUrl', $attributes)) { - $this->assertPermission($canEdit); - - $this->avatarUploader->remove($user); - } - $this->events->dispatch( new Saving($user, $actor, $data) ); diff --git a/framework/core/src/User/Command/RegisterUserHandler.php b/framework/core/src/User/Command/RegisterUserHandler.php index 05029bb70..a8899a55d 100644 --- a/framework/core/src/User/Command/RegisterUserHandler.php +++ b/framework/core/src/User/Command/RegisterUserHandler.php @@ -11,18 +11,17 @@ namespace Flarum\User\Command; -use Exception; use Flarum\Foundation\DispatchEventsTrait; use Flarum\Settings\SettingsRepositoryInterface; use Flarum\User\AssertPermissionTrait; -use Flarum\User\AuthToken; use Flarum\User\AvatarUploader; +use Flarum\User\Event\RegisteringFromProvider; use Flarum\User\Event\Saving; use Flarum\User\Exception\PermissionDeniedException; +use Flarum\User\RegistrationToken; use Flarum\User\User; use Flarum\User\UserValidator; use Illuminate\Contracts\Events\Dispatcher; -use Illuminate\Contracts\Validation\Factory; use Illuminate\Validation\ValidationException; use Intervention\Image\ImageManager; @@ -46,34 +45,26 @@ class RegisterUserHandler */ protected $avatarUploader; - /** - * @var Factory - */ - private $validatorFactory; - /** * @param Dispatcher $events * @param SettingsRepositoryInterface $settings * @param UserValidator $validator * @param AvatarUploader $avatarUploader - * @param Factory $validatorFactory */ - public function __construct(Dispatcher $events, SettingsRepositoryInterface $settings, UserValidator $validator, AvatarUploader $avatarUploader, Factory $validatorFactory) + public function __construct(Dispatcher $events, SettingsRepositoryInterface $settings, UserValidator $validator, AvatarUploader $avatarUploader) { $this->events = $events; $this->settings = $settings; $this->validator = $validator; $this->avatarUploader = $avatarUploader; - $this->validatorFactory = $validatorFactory; } /** * @param RegisterUser $command + * @return User * @throws PermissionDeniedException if signup is closed and the actor is * not an administrator. - * @throws \Flarum\User\Exception\InvalidConfirmationTokenException if an - * email confirmation token is provided but is invalid. - * @return User + * @throws ValidationException */ public function handle(RegisterUser $command) { @@ -84,32 +75,24 @@ class RegisterUserHandler $this->assertAdmin($actor); } - $username = array_get($data, 'attributes.username'); - $email = array_get($data, 'attributes.email'); $password = array_get($data, 'attributes.password'); // If a valid authentication token was provided as an attribute, // then we won't require the user to choose a password. if (isset($data['attributes']['token'])) { - $token = AuthToken::validOrFail($data['attributes']['token']); + $token = RegistrationToken::validOrFail($data['attributes']['token']); $password = $password ?: str_random(20); } - $user = User::register($username, $email, $password); + $user = User::register( + array_get($data, 'attributes.username'), + array_get($data, 'attributes.email'), + $password + ); - // If a valid authentication token was provided, then we will assign - // the attributes associated with it to the user's account. If this - // includes an email address, then we will activate the user's account - // from the get-go. if (isset($token)) { - foreach ($token->payload as $k => $v) { - $user->$k = $v; - } - - if (isset($token->payload['email'])) { - $user->activate(); - } + $this->applyToken($user, $token); } if ($actor->isAdmin() && array_get($data, 'attributes.isEmailConfirmed')) { @@ -122,30 +105,53 @@ class RegisterUserHandler $this->validator->assertValid(array_merge($user->getAttributes(), compact('password'))); - if ($avatarUrl = array_get($data, 'attributes.avatarUrl')) { - $validation = $this->validatorFactory->make(compact('avatarUrl'), ['avatarUrl' => 'url']); - - if ($validation->fails()) { - throw new ValidationException($validation); - } - - try { - $image = (new ImageManager)->make($avatarUrl); - - $this->avatarUploader->upload($user, $image); - } catch (Exception $e) { - // - } - } - $user->save(); if (isset($token)) { - $token->delete(); + $this->fulfillToken($user, $token); } $this->dispatchEventsFor($user, $actor); return $user; } + + private function applyToken(User $user, RegistrationToken $token) + { + foreach ($token->user_attributes as $k => $v) { + if ($k === 'avatar_url') { + $this->uploadAvatarFromUrl($user, $v); + continue; + } + + $user->$k = $v; + + if ($k === 'email') { + $user->activate(); + } + } + + $this->events->dispatch( + new RegisteringFromProvider($user, $token->provider, $token->payload) + ); + } + + private function uploadAvatarFromUrl(User $user, string $url) + { + $image = (new ImageManager)->make($url); + + $this->avatarUploader->upload($user, $image); + } + + private function fulfillToken(User $user, RegistrationToken $token) + { + $token->delete(); + + if ($token->provider && $token->identifier) { + $user->loginProviders()->create([ + 'provider' => $token->provider, + 'identifier' => $token->identifier + ]); + } + } } diff --git a/framework/core/src/User/Event/RegisteringFromProvider.php b/framework/core/src/User/Event/RegisteringFromProvider.php new file mode 100644 index 000000000..6b96a3bff --- /dev/null +++ b/framework/core/src/User/Event/RegisteringFromProvider.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\User\Event; + +use Flarum\User\User; + +class RegisteringFromProvider +{ + /** + * @var User + */ + public $user; + + /** + * @var string + */ + public $provider; + + /** + * @var array + */ + public $payload; + + /** + * @param User $user + * @param $provider + * @param $payload + */ + public function __construct(User $user, string $provider, array $payload) + { + $this->user = $user; + $this->provider = $provider; + $this->payload = $payload; + } +} diff --git a/framework/core/src/User/LoginProvider.php b/framework/core/src/User/LoginProvider.php new file mode 100644 index 000000000..e0fb1bf8c --- /dev/null +++ b/framework/core/src/User/LoginProvider.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\User; + +use Flarum\Database\AbstractModel; + +/** + * @property int $id + * @property int $user_id + * @property string $provider + * @property string $identifier + * @property \Illuminate\Support\Carbon $created_at + * @property \Illuminate\Support\Carbon $last_login_at + * @property-read User $user + */ +class LoginProvider extends AbstractModel +{ + protected $dates = ['created_at', 'last_login_at']; + + public $timestamps = true; + + const UPDATED_AT = 'last_login_at'; + + protected $fillable = ['provider', 'identifier']; + + /** + * Get the user that the login provider belongs to. + */ + public function user() + { + return $this->belongsTo(User::class); + } + + /** + * Get the user associated with the provider so that they can be logged in. + * + * @param string $provider + * @param string $identifier + * @return User|null + */ + public static function logIn(string $provider, string $identifier): ?User + { + if ($provider = static::where(compact('provider', 'identifier'))->first()) { + $provider->touch(); + + return $provider->user; + } + + return null; + } +} diff --git a/framework/core/src/User/AuthToken.php b/framework/core/src/User/RegistrationToken.php similarity index 64% rename from framework/core/src/User/AuthToken.php rename to framework/core/src/User/RegistrationToken.php index 877cd7c24..61c6a5144 100644 --- a/framework/core/src/User/AuthToken.php +++ b/framework/core/src/User/RegistrationToken.php @@ -17,16 +17,14 @@ use Flarum\User\Exception\InvalidConfirmationTokenException; /** * @property string $token + * @property string $provider + * @property string $identifier + * @property array $user_attributes + * @property array $payload * @property \Carbon\Carbon $created_at - * @property string $payload */ -class AuthToken extends AbstractModel +class RegistrationToken extends AbstractModel { - /** - * {@inheritdoc} - */ - protected $table = 'registration_tokens'; - /** * The attributes that should be mutated to dates. * @@ -34,6 +32,11 @@ class AuthToken extends AbstractModel */ protected $dates = ['created_at']; + protected $casts = [ + 'user_attributes' => 'array', + 'payload' => 'array' + ]; + /** * Use a custom primary key for this model. * @@ -47,44 +50,28 @@ class AuthToken extends AbstractModel protected $primaryKey = 'token'; /** - * Generate an email token for the specified user. - * - * @param string $payload + * Generate an auth token for the specified user. * + * @param string $provider + * @param string $identifier + * @param array $attributes + * @param array $payload * @return static */ - public static function generate($payload) + public static function generate(string $provider, string $identifier, array $attributes, array $payload) { $token = new static; $token->token = str_random(40); + $token->provider = $provider; + $token->identifier = $identifier; + $token->user_attributes = $attributes; $token->payload = $payload; $token->created_at = Carbon::now(); return $token; } - /** - * Unserialize the payload attribute from the database's JSON value. - * - * @param string $value - * @return string - */ - public function getPayloadAttribute($value) - { - return json_decode($value, true); - } - - /** - * Serialize the payload attribute to be stored in the database as JSON. - * - * @param string $value - */ - public function setPayloadAttribute($value) - { - $this->attributes['payload'] = json_encode($value); - } - /** * Find the token with the given ID, and assert that it has not expired. * @@ -93,11 +80,11 @@ class AuthToken extends AbstractModel * * @throws InvalidConfirmationTokenException * - * @return AuthToken + * @return RegistrationToken */ public function scopeValidOrFail($query, string $token) { - /** @var AuthToken $token */ + /** @var RegistrationToken $token */ $token = $query->find($token); if (! $token || $token->created_at->lessThan(Carbon::now()->subDay())) { diff --git a/framework/core/src/User/User.php b/framework/core/src/User/User.php index b4742e212..cd3b7c035 100644 --- a/framework/core/src/User/User.php +++ b/framework/core/src/User/User.php @@ -668,6 +668,14 @@ class User extends AbstractModel return $this->hasMany('Flarum\Http\AccessToken'); } + /** + * Get the user's login providers. + */ + public function loginProviders() + { + return $this->hasMany(LoginProvider::class); + } + /** * @param string $ability * @param array|mixed $arguments