diff --git a/framework/core/js/forum/src/components/LogInButton.js b/framework/core/js/forum/src/components/LogInButton.js index 3de450589..f936204fb 100644 --- a/framework/core/js/forum/src/components/LogInButton.js +++ b/framework/core/js/forum/src/components/LogInButton.js @@ -13,8 +13,8 @@ export default class LogInButton extends Button { props.className = (props.className || '') + ' LogInButton'; props.onclick = function() { - const width = 620; - const height = 400; + const width = 1000; + const height = 500; const $window = $(window); window.open(app.forum.attribute('baseUrl') + props.path, 'logInPopup', diff --git a/framework/core/js/forum/src/components/SignUpModal.js b/framework/core/js/forum/src/components/SignUpModal.js index b66e35f3d..19899560e 100644 --- a/framework/core/js/forum/src/components/SignUpModal.js +++ b/framework/core/js/forum/src/components/SignUpModal.js @@ -82,7 +82,7 @@ export default class SignUpModal extends Modal { + disabled={this.loading || (this.props.token && this.props.email)} /> {this.props.token ? '' : ( @@ -121,7 +121,8 @@ export default class SignUpModal extends Modal { {avatar(user)}

{app.trans('core.welcome_user', {user})}

-

{app.trans('core.confirmation_email_sent', {email: {user.email()}})}

, +

{app.trans('core.confirmation_email_sent', {email: {user.email()}})}

+

{app.trans('core.go_to', {location: emailProviderName})} @@ -161,7 +162,7 @@ export default class SignUpModal extends Modal { } onready() { - if (this.props.username && !this.props.token) { + if (this.props.username && !this.props.email) { this.$('[name=email]').select(); } else { this.$('[name=username]').select(); diff --git a/framework/core/migrations/2015_09_15_000000_make_email_tokens_user_id_column_nullable.php b/framework/core/migrations/2015_09_15_000000_create_auth_tokens_table.php similarity index 61% rename from framework/core/migrations/2015_09_15_000000_make_email_tokens_user_id_column_nullable.php rename to framework/core/migrations/2015_09_15_000000_create_auth_tokens_table.php index 5e8ced5e1..e5e1d55f8 100644 --- a/framework/core/migrations/2015_09_15_000000_make_email_tokens_user_id_column_nullable.php +++ b/framework/core/migrations/2015_09_15_000000_create_auth_tokens_table.php @@ -12,7 +12,7 @@ use Flarum\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; -class MakeEmailTokensUserIdColumnNullable extends Migration +class CreateAuthTokensTable extends Migration { /** * Run the migrations. @@ -21,8 +21,10 @@ class MakeEmailTokensUserIdColumnNullable extends Migration */ public function up() { - $this->schema->table('email_tokens', function (Blueprint $table) { - $table->integer('user_id')->unsigned()->nullable()->change(); + $this->schema->create('auth_tokens', function (Blueprint $table) { + $table->string('id', 100)->primary(); + $table->string('payload', 150); + $table->timestamp('created_at'); }); } @@ -33,8 +35,6 @@ class MakeEmailTokensUserIdColumnNullable extends Migration */ public function down() { - $this->schema->table('email_tokens', function (Blueprint $table) { - $table->integer('user_id')->unsigned()->change(); - }); + $this->schema->drop('auth_tokens'); } } diff --git a/framework/core/src/Core/Users/AuthToken.php b/framework/core/src/Core/Users/AuthToken.php new file mode 100644 index 000000000..04c9f7174 --- /dev/null +++ b/framework/core/src/Core/Users/AuthToken.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Core\Users; + +use Flarum\Core\Model; +use Flarum\Core\Exceptions\InvalidConfirmationTokenException; +use DateTime; + +/** + * @todo document database columns with @property + */ +class AuthToken extends Model +{ + /** + * {@inheritdoc} + */ + protected $table = 'auth_tokens'; + + /** + * {@inheritdoc} + */ + protected $dates = ['created_at']; + + /** + * Use a custom primary key for this model. + * + * @var bool + */ + public $incrementing = false; + + /** + * Generate an email token for the specified user. + * + * @param string $email + * + * @return static + */ + public static function generate($payload) + { + $token = new static; + + $token->id = str_random(40); + $token->payload = $payload; + $token->created_at = time(); + + 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. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param string $id + * + * @throws InvalidConfirmationTokenException + * + * @return static + */ + public function scopeValidOrFail($query, $id) + { + $token = $query->find($id); + + if (! $token || $token->created_at < new DateTime('-1 day')) { + throw new InvalidConfirmationTokenException; + } + + return $token; + } +} diff --git a/framework/core/src/Core/Users/Commands/RegisterUserHandler.php b/framework/core/src/Core/Users/Commands/RegisterUserHandler.php index 132a400e0..9d064c748 100644 --- a/framework/core/src/Core/Users/Commands/RegisterUserHandler.php +++ b/framework/core/src/Core/Users/Commands/RegisterUserHandler.php @@ -11,7 +11,7 @@ namespace Flarum\Core\Users\Commands; use Flarum\Core\Users\User; -use Flarum\Core\Users\EmailToken; +use Flarum\Core\Users\AuthToken; use Flarum\Events\UserWillBeSaved; use Flarum\Core\Support\DispatchesEvents; use Flarum\Core\Settings\SettingsRepository; @@ -54,30 +54,36 @@ class RegisterUserHandler throw new PermissionDeniedException; } - // If a valid email confirmation token was provided as an attribute, - // then we can create a random password for this user and consider their - // email address confirmed. - if (isset($data['attributes']['token'])) { - $token = EmailToken::whereNull('user_id')->validOrFail($data['attributes']['token']); + $username = array_get($data, 'attributes.username'); + $email = array_get($data, 'attributes.email'); + $password = array_get($data, 'attributes.password'); - $email = $token->email; - $password = array_get($data, 'attributes.password', str_random(20)); - } else { - $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']); + + $password = $password ?: str_random(20); } - // Create the user's new account. If their email was set via token, then - // we can activate their account from the get-go, and they won't need - // to confirm their email address. $user = User::register( - array_get($data, 'attributes.username'), + $username, $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)) { - $user->activate(); + foreach ($token->payload as $k => $v) { + $user->$k = $v; + } + + if (isset($token->payload['email'])) { + $user->activate(); + } } event(new UserWillBeSaved($user, $actor, $data)); diff --git a/framework/core/src/Core/Users/EmailToken.php b/framework/core/src/Core/Users/EmailToken.php index eb61b0cd0..a5d03d4bb 100644 --- a/framework/core/src/Core/Users/EmailToken.php +++ b/framework/core/src/Core/Users/EmailToken.php @@ -44,7 +44,7 @@ class EmailToken extends Model * * @return static */ - public static function generate($email, $userId = null) + public static function generate($email, $userId) { $token = new static; diff --git a/framework/core/src/Forum/Actions/ExternalAuthenticatorTrait.php b/framework/core/src/Forum/Actions/ExternalAuthenticatorTrait.php index 0ed0337ef..601a8c566 100644 --- a/framework/core/src/Forum/Actions/ExternalAuthenticatorTrait.php +++ b/framework/core/src/Forum/Actions/ExternalAuthenticatorTrait.php @@ -13,7 +13,7 @@ namespace Flarum\Forum\Actions; use Flarum\Core\Users\User; use Zend\Diactoros\Response\HtmlResponse; use Flarum\Api\Commands\GenerateAccessToken; -use Flarum\Core\Users\EmailToken; +use Flarum\Core\Users\AuthToken; trait ExternalAuthenticatorTrait { @@ -25,33 +25,42 @@ trait ExternalAuthenticatorTrait protected $bus; /** - * Respond with JavaScript to tell the Flarum app that the user has been - * authenticated, or with information about their sign up status. + * Respond with JavaScript to inform the Flarum app about the user's + * authentication status. * - * @param string $email The email of the user's account. - * @param string $username A suggested username for the user's account. + * An array of identification attributes must be passed as the first + * argument. These are checked against existing user accounts; if a match is + * found, then the user is authenticated and logged into that account via + * cookie. The Flarum app will then simply refresh the page. + * + * If no matching account is found, then an AuthToken will be generated to + * store the identification attributes. This token, along with an optional + * array of suggestions, will be passed into the Flarum app's sign up modal. + * This results in the user not having to choose a password. When they + * complete their registration, the identification attributes will be + * set on their new user account. + * + * @param array $identification + * @param array $suggestions * @return HtmlResponse */ - protected function authenticated($email, $username) + protected function authenticated(array $identification, array $suggestions = []) { - $user = User::where('email', $email)->first(); + $user = User::where($identification)->first(); - // If a user with this email address doesn't already exist, then we will - // generate a unique confirmation token for this email address and add - // it to the response, along with the email address and a suggested - // username. Otherwise, we will log in the existing user by generating - // an access token. - if (! $user) { - $token = EmailToken::generate($email); - $token->save(); - - $payload = compact('email', 'username'); - - $payload['token'] = $token->id; - } else { + // 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) { $accessToken = $this->bus->dispatch(new GenerateAccessToken($user->id)); $payload = ['authenticated' => true]; + } else { + $token = AuthToken::generate($identification); + $token->save(); + + $payload = array_merge($identification, $suggestions, ['token' => $token->id]); } $content = sprintf('