diff --git a/framework/core/js/forum/src/components/change-password-modal.js b/framework/core/js/forum/src/components/change-password-modal.js index b77591dd8..d8103466b 100644 --- a/framework/core/js/forum/src/components/change-password-modal.js +++ b/framework/core/js/forum/src/components/change-password-modal.js @@ -8,7 +8,7 @@ export default class ChangePasswordModal extends FormModal { body: [ m('p.help-text', 'Click the button below and check your email for a link to change your password.'), m('div.form-group', [ - m('button.btn.btn-primary.btn-block[type=submit]', 'Send Password Reset Email') + m('button.btn.btn-primary.btn-block[type=submit]', {disabled: this.loading()}, 'Send Password Reset Email') ]) ] }); diff --git a/framework/core/js/forum/src/components/forgot-password-modal.js b/framework/core/js/forum/src/components/forgot-password-modal.js index cdfa74593..92a7949e0 100644 --- a/framework/core/js/forum/src/components/forgot-password-modal.js +++ b/framework/core/js/forum/src/components/forgot-password-modal.js @@ -21,13 +21,13 @@ export default class ForgotPasswordModal extends FormModal { title: 'Forgot Password', body: this.success() ? [ - m('p.help-text', 'OK, we\'ve sent you an email containing a link to reset your password. Check your spam folder if you don\'t receive it within the next minute or two. Yeah, sometimes we get put through to spam - can you believe it?!'), + m('p.help-text', 'We\'ve sent you an email containing a link to reset your password. Check your spam folder if you don\'t receive it within the next minute or two.'), m('div.form-group', [ m('a.btn.btn-primary.btn-block', {href: 'http://'+emailProviderName}, 'Go to '+emailProviderName) ]) ] : [ - m('p.help-text', 'Forgot your password? Don\'t worry, it happens all the time. Simply enter your email address and we\'ll send you instructions on how to set up a new one.'), + m('p.help-text', 'Enter your email address and we\'ll send you a link to reset your password.'), m('div.form-group', [ m('input.form-control[name=email][placeholder=Email]', {value: this.email(), onchange: m.withAttr('value', this.email), disabled: this.loading()}) ]), @@ -57,12 +57,11 @@ export default class ForgotPasswordModal extends FormModal { }).then(response => { this.loading(false); this.success(true); - this.alert = null; + this.alert(null); m.redraw(); }, response => { this.loading(false); - m.redraw(); - this.ready(); + this.handleErrors(response.errors); }); } } diff --git a/framework/core/js/forum/src/components/form-modal.js b/framework/core/js/forum/src/components/form-modal.js index b32babd2c..bfae1bbf7 100644 --- a/framework/core/js/forum/src/components/form-modal.js +++ b/framework/core/js/forum/src/components/form-modal.js @@ -7,13 +7,14 @@ export default class FormModal extends Component { constructor(props) { super(props); - this.alert = null; + this.alert = m.prop(); this.loading = m.prop(false); } view(options) { - if (this.alert) { - this.alert.props.dismissible = false; + var alert = this.alert(); + if (alert) { + alert.props.dismissible = false; } return m('div.modal-dialog', {className: options.className, config: this.element}, [ @@ -21,7 +22,7 @@ export default class FormModal extends Component { m('a[href=javascript:;].btn.btn-icon.btn-link.close.back-control', {onclick: this.hide.bind(this)}, icon('times')), m('form', {onsubmit: this.onsubmit.bind(this)}, [ m('div.modal-header', m('h3.title-control', options.title)), - this.alert ? m('div.modal-alert', this.alert.view()) : '', + alert ? m('div.modal-alert', alert) : '', m('div.modal-body', [ m('div.form-centered', options.body) ]), @@ -39,4 +40,19 @@ export default class FormModal extends Component { hide() { app.modal.close(); } + + handleErrors(errors) { + if (errors) { + this.alert(new Alert({ + type: 'warning', + message: errors.map((error, k) => [error.detail, k < errors.length - 1 ? m('br') : '']) + })); + } + + m.redraw(); + + if (errors) { + this.$('[name='+errors[0].path+']').select(); + } + } } diff --git a/framework/core/js/forum/src/components/signup-modal.js b/framework/core/js/forum/src/components/signup-modal.js index 8777b991e..6b8ddd545 100644 --- a/framework/core/js/forum/src/components/signup-modal.js +++ b/framework/core/js/forum/src/components/signup-modal.js @@ -67,6 +67,14 @@ export default class SignupModal extends FormModal { return vdom; } + ready() { + if (this.props.username) { + this.$('[name=email]').select(); + } else { + super.ready(); + } + } + fadeIn(element, isInitialized) { if (isInitialized) { return; } $(element).hide().fadeIn(); @@ -86,9 +94,7 @@ export default class SignupModal extends FormModal { m.redraw(); }, response => { this.loading(false); - this.alert = new Alert({ type: 'warning', message: response.errors.map((error, k) => [error.detail, k < response.errors.length - 1 ? m('br') : '']) }); - m.redraw(); - this.$('[name='+response.errors[0].path+']').select(); + this.handleErrors(response.errors); }); } } diff --git a/framework/core/js/lib/model.js b/framework/core/js/lib/model.js index 376a1742e..ecfbb5bc3 100644 --- a/framework/core/js/lib/model.js +++ b/framework/core/js/lib/model.js @@ -34,6 +34,21 @@ export default class Model { } } + // clone the relevant parts of the model's old data so that we can revert + // back if the save fails + var oldData = {}; + var currentData = this.data(); + for (var i in data) { + if (i === 'links') { + oldData[i] = oldData[i] || {}; + for (var j in newData[i]) { + oldData[i][j] = currentData[i][j]; + } + } else { + oldData[i] = currentData[i]; + } + } + this.pushData(data); return app.request({ @@ -45,6 +60,9 @@ export default class Model { }).then(payload => { this.store.data[payload.data.type][payload.data.id] = this; return this.store.pushPayload(payload); + }, response => { + this.pushData(oldData); + throw response; }); } diff --git a/framework/core/migrations/2015_02_24_000000_create_access_tokens_table.php b/framework/core/migrations/2015_02_24_000000_create_access_tokens_table.php index 24b942eb6..08a123745 100644 --- a/framework/core/migrations/2015_02_24_000000_create_access_tokens_table.php +++ b/framework/core/migrations/2015_02_24_000000_create_access_tokens_table.php @@ -5,7 +5,6 @@ use Illuminate\Database\Migrations\Migration; class CreateAccessTokensTable extends Migration { - /** * Run the migrations. * @@ -14,9 +13,10 @@ class CreateAccessTokensTable extends Migration public function up() { Schema::create('access_tokens', function (Blueprint $table) { - $table->string('id', 100)->primary(); $table->integer('user_id')->unsigned(); + $table->timestamp('created_at'); + $table->timestamp('expires_at'); }); } diff --git a/framework/core/migrations/2015_02_24_000000_create_reset_tokens_table.php b/framework/core/migrations/2015_02_24_000000_create_password_tokens_table.php similarity index 59% rename from framework/core/migrations/2015_02_24_000000_create_reset_tokens_table.php rename to framework/core/migrations/2015_02_24_000000_create_password_tokens_table.php index 63c2c5b5d..5d3ec844f 100644 --- a/framework/core/migrations/2015_02_24_000000_create_reset_tokens_table.php +++ b/framework/core/migrations/2015_02_24_000000_create_password_tokens_table.php @@ -3,7 +3,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; -class CreateResetTokensTable extends Migration +class CreatePasswordTokensTable extends Migration { /** * Run the migrations. @@ -12,9 +12,10 @@ class CreateResetTokensTable extends Migration */ public function up() { - Schema::create('reset_tokens', function (Blueprint $table) { - $table->string('id'); + Schema::create('password_tokens', function (Blueprint $table) { + $table->string('id', 100)->primary(); $table->integer('user_id')->unsigned(); + $table->timestamp('created_at'); }); } @@ -25,6 +26,6 @@ class CreateResetTokensTable extends Migration */ public function down() { - Schema::drop('reset_tokens'); + Schema::drop('password_tokens'); } } diff --git a/framework/core/src/Console/SeedCommand.php b/framework/core/src/Console/SeedCommand.php index f8db5eee4..3b5957479 100644 --- a/framework/core/src/Console/SeedCommand.php +++ b/framework/core/src/Console/SeedCommand.php @@ -41,8 +41,8 @@ class SeedCommand extends Command */ public function fire() { - $this->call('db:seed', ['--class' => 'Flarum\Core\Seeders\DiscussionsTableSeeder']); $this->call('db:seed', ['--class' => 'Flarum\Core\Seeders\UsersTableSeeder']); + $this->call('db:seed', ['--class' => 'Flarum\Core\Seeders\DiscussionsTableSeeder']); } /** diff --git a/framework/core/src/Core.php b/framework/core/src/Core.php index 83262ba9a..bf77ed751 100644 --- a/framework/core/src/Core.php +++ b/framework/core/src/Core.php @@ -11,6 +11,10 @@ class Core public static function config($key, $default = null) { + if (! static::isInstalled()) { + return $default; + } + if (is_null($value = DB::table('config')->where('key', $key)->pluck('value'))) { $value = $default; } diff --git a/framework/core/src/Core/Handlers/Commands/RequestPasswordResetCommandHandler.php b/framework/core/src/Core/Handlers/Commands/RequestPasswordResetCommandHandler.php index ad636d8f2..b8e7e3c2b 100644 --- a/framework/core/src/Core/Handlers/Commands/RequestPasswordResetCommandHandler.php +++ b/framework/core/src/Core/Handlers/Commands/RequestPasswordResetCommandHandler.php @@ -1,10 +1,11 @@ id); + $token = PasswordToken::generate($user->id); $token->save(); $data = [ 'username' => $user->username, - 'url' => route('flarum.forum.resetPassword', ['token' => $token->id]) + 'url' => route('flarum.forum.resetPassword', ['token' => $token->id]), + 'forumTitle' => Core::config('forum_title') ]; - $this->mailer->send(['text' => 'flarum::emails.reset'], $data, function ($message) use ($user) { + $this->mailer->send(['text' => 'flarum::emails.resetPassword'], $data, function ($message) use ($user) { $message->to($user->email); $message->subject('Reset Your Password'); }); diff --git a/framework/core/src/Core/Models/AccessToken.php b/framework/core/src/Core/Models/AccessToken.php index 195f5ed84..7adb8f652 100644 --- a/framework/core/src/Core/Models/AccessToken.php +++ b/framework/core/src/Core/Models/AccessToken.php @@ -16,18 +16,28 @@ class AccessToken extends Model */ public $incrementing = false; + /** + * The attributes that should be mutated to dates. + * + * @var array + */ + protected $dates = ['created_at', 'expires_at']; + /** * Generate an access token for the specified user. * * @param int $userId + * @param int $minutes * @return static */ - public static function generate($userId) + public static function generate($userId, $minutes = 60) { $token = new static; $token->id = str_random(40); $token->user_id = $userId; + $token->created_at = time(); + $token->expires_at = time() + $minutes * 60; return $token; } diff --git a/framework/core/src/Core/Models/ResetToken.php b/framework/core/src/Core/Models/PasswordToken.php similarity index 87% rename from framework/core/src/Core/Models/ResetToken.php rename to framework/core/src/Core/Models/PasswordToken.php index 964f2799d..6896a395e 100644 --- a/framework/core/src/Core/Models/ResetToken.php +++ b/framework/core/src/Core/Models/PasswordToken.php @@ -1,13 +1,13 @@ id = str_random(40); $token->user_id = $userId; + $token->created_at = time(); return $token; } diff --git a/framework/core/src/Forum/Actions/ResetPasswordAction.php b/framework/core/src/Forum/Actions/ResetPasswordAction.php index 7344c4128..bc88f5b43 100644 --- a/framework/core/src/Forum/Actions/ResetPasswordAction.php +++ b/framework/core/src/Forum/Actions/ResetPasswordAction.php @@ -1,6 +1,6 @@ with('token', $token->id); } diff --git a/framework/core/src/Forum/Actions/SavePasswordAction.php b/framework/core/src/Forum/Actions/SavePasswordAction.php index 137a9eddc..69666cdb3 100644 --- a/framework/core/src/Forum/Actions/SavePasswordAction.php +++ b/framework/core/src/Forum/Actions/SavePasswordAction.php @@ -1,6 +1,6 @@ get('token')); + $token = PasswordToken::findOrFail($request->get('token')); $password = $request->get('password'); $confirmation = $request->get('password_confirmation'); diff --git a/framework/core/views/emails/reset.blade.php b/framework/core/views/emails/reset.blade.php deleted file mode 100644 index 75b2bcb2d..000000000 --- a/framework/core/views/emails/reset.blade.php +++ /dev/null @@ -1,3 +0,0 @@ -Hey {{ $username }}! - -Click here to reset your password: {{ $url }} diff --git a/framework/core/views/emails/resetPassword.blade.php b/framework/core/views/emails/resetPassword.blade.php new file mode 100644 index 000000000..1f808d99e --- /dev/null +++ b/framework/core/views/emails/resetPassword.blade.php @@ -0,0 +1,8 @@ +Hey {{ $username }}! + +Someone (hopefully you!) has submitted a forgotten password request for your account on the {{ $forumTitle }}. + +If this was you, click the following link to reset your password: +{{ $url }} + +If you do not wish to change your password, just ignore this email and nothing will happen.