Skip to content

Commit 2d353fd

Browse files
committed
PasswordController tests & functionality #129
1 parent f304d02 commit 2d353fd

File tree

9 files changed

+214
-15
lines changed

9 files changed

+214
-15
lines changed

angular/app/components/reset-password/reset-password.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<div class="ResetPassword-loader" layout="row" layout-align="center center">
2-
<md-progress-circular md-mode="indeterminate" ng-if="!vm.isValidCode"></md-progress-circular>
2+
<md-progress-circular md-mode="indeterminate" ng-if="!vm.isValidToken"></md-progress-circular>
33
</div>
44

5-
<form ng-submit="vm.submit()" ng-show="vm.isValidCode">
5+
<form ng-submit="vm.submit()" ng-show="vm.isValidToken">
66
<div>
77
<md-input-container>
88
<label>Password</label>

angular/app/components/reset-password/reset-password.component.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,31 +10,31 @@ class ResetPasswordController {
1010
$onInit(){
1111
this.password = '';
1212
this.password_confirmation = '';
13-
this.isValidCode = false;
13+
this.isValidToken = false;
1414

15-
this.validateCode();
15+
this.verifyToken();
1616
}
1717

18-
validateCode() {
18+
verifyToken() {
1919
let email = this.$state.params.email;
20-
let code = this.$state.params.code;
20+
let token = this.$state.params.token;
2121

22-
this.API.all('auth/reset').get('check', {
23-
email, code
22+
this.API.all('auth/password').get('verify', {
23+
email, token
2424
}).then(() => {
25-
this.isValidCode = true;
25+
this.isValidToken = true;
2626
});
2727
}
2828

2929
reset() {
3030
let data = {
3131
email: this.$state.params.email,
32-
code: this.$state.params.code,
32+
token: this.$state.params.token,
3333
password: this.password,
3434
password_confirmation: this.password_confirmation
3535
};
3636

37-
this.API.all('auth/reset').post(data).then(() => {
37+
this.API.all('auth/password/reset').post(data).then(() => {
3838
this.ToastService.show('Password successfully changed');
3939
this.$state.go('app.login');
4040
});

angular/config/routes.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export function RoutesConfig($stateProvider, $urlRouterProvider) {
5454
}
5555
})
5656
.state('app.reset_password', {
57-
url: '/reset-password/:email/:code',
57+
url: '/reset-password/:email/:token',
5858
views: {
5959
'main@': {
6060
templateUrl: getView('reset-password')
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Auth;
4+
5+
use Mail;
6+
use App\User;
7+
use App\PasswordReset;
8+
use Illuminate\Http\Request;
9+
use App\Http\Controllers\Controller;
10+
11+
class PasswordResetController extends Controller
12+
{
13+
public function sendResetLinkEmail(Request $request)
14+
{
15+
$this->validate($request, [
16+
'email' => 'required|email|exists:users,email',
17+
]);
18+
19+
$email = $request->email;
20+
$reset = PasswordReset::create([
21+
'email' => $email,
22+
'token' => str_random(10)
23+
]);
24+
25+
$token = $reset->token;
26+
27+
Mail::send('auth.reset_link', compact('email', 'token'), function ($mail) use ($email) {
28+
$mail->to($email)
29+
->subject('Password reset link');
30+
});
31+
32+
return response()->success(true);
33+
}
34+
35+
public function verify(Request $request)
36+
{
37+
$this->validate($request, [
38+
'email' => 'required|email',
39+
'token' => 'required',
40+
]);
41+
42+
$check = PasswordReset::whereEmail($request->email)
43+
->whereToken($request->token)
44+
->first();
45+
46+
if (!$check) {
47+
return response()->error('Email does not exist', 422);
48+
}
49+
return response()->success(true);
50+
}
51+
52+
public function reset(Request $request)
53+
{
54+
$this->validate($request, [
55+
'email' => 'required|email',
56+
'token' => "required|exists:password_resets,token,email,{$request->email}",
57+
'password' => 'required|min:8|confirmed',
58+
]);
59+
60+
$user = User::whereEmail($request->email)->firstOrFail();
61+
$user->password = bcrypt($request->password);
62+
$user->save();
63+
64+
return response()->success(true);
65+
}
66+
}

app/Http/routes.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,15 @@
2222
//public API routes
2323
$api->group(['middleware' => ['api']], function ($api) {
2424

25+
// Authentication Routes...
2526
$api->post('auth/login', 'Auth\AuthController@login');
26-
2727
$api->post('auth/register', 'Auth\AuthController@register');
2828

29+
// Password Reset Routes...
30+
$api->post('auth/password/email', 'Auth\PasswordResetController@sendResetLinkEmail');
31+
$api->post('auth/password/verify', 'Auth\PasswordResetController@verify');
32+
$api->post('auth/password/reset', 'Auth\PasswordResetController@reset');
33+
2934
});
3035

3136
//protected API routes with JWT (must be logged in)

app/PasswordReset.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace App;
4+
5+
use Illuminate\Database\Eloquent\Model;
6+
7+
class PasswordReset extends Model
8+
{
9+
10+
protected $fillable = ['email', 'token'];
11+
12+
public function setUpdatedAtAttribute($value)
13+
{
14+
// to disable updated_at
15+
}
16+
}

database/factories/ModelFactory.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,16 @@
1313

1414
$factory->define(App\User::class, function (Faker\Generator $faker) {
1515
return [
16-
'name' => $faker->name,
17-
'email' => $faker->email,
16+
'name' => $faker->name,
17+
'email' => $faker->safeEmail,
1818
//use bcrypt('password') if you want to assert for a specific password, but it might slow down your tests
1919
'password' => str_random(10),
2020
];
2121
});
22+
23+
$factory->define(App\PasswordReset::class, function (Faker\Generator $faker) {
24+
return [
25+
'email' => $faker->safeEmail,
26+
'token' => str_random(10),
27+
];
28+
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Your reset password link:
2+
3+
http://localhost:8000/reset-password/{{$email}}/{{$token}}

tests/PasswordResetTest.php

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?php
2+
3+
use App\PasswordReset;
4+
use Illuminate\Foundation\Testing\DatabaseTransactions;
5+
6+
class PasswordResetTest extends TestCase
7+
{
8+
use DatabaseTransactions;
9+
10+
public function testSendEmailWithTokenForResetPassword()
11+
{
12+
$user = factory(App\User::class)->create();
13+
14+
$this->checkEmailContent([
15+
'title' => 'Password reset link',
16+
'email' => $user->email,
17+
'content' => 'reset password link', //contains
18+
]);
19+
20+
$this->post('/api/auth/password/email', ['email' => $user->email])
21+
->seeApiSuccess();
22+
}
23+
24+
public function testVerifyTokenSuccessfully()
25+
{
26+
$reset = factory(PasswordReset::class)->create();
27+
28+
$this->post('/api/auth/password/verify', [
29+
'email' => $reset->email,
30+
'token' => $reset->token,
31+
])
32+
->seeApiSuccess();
33+
}
34+
35+
public function testVerifyTokenUnsuccessfully()
36+
{
37+
$reset = factory(PasswordReset::class)->create();
38+
39+
$this->post('/api/auth/password/verify', [
40+
'email' => $reset->email,
41+
'token' => str_random(10),
42+
])
43+
->seeValidationError();
44+
}
45+
46+
47+
public function testResetPasswordWithTokenSuccessfully()
48+
{
49+
$user = factory(App\User::class)->create();
50+
$reset = factory(PasswordReset::class)->create([
51+
'email' => $user->email,
52+
]);
53+
54+
$newPassword = str_random(10);
55+
56+
$this->post('/api/auth/password/reset', [
57+
'email' => $reset->email,
58+
'token' => $reset->token,
59+
'password' => $newPassword,
60+
'password_confirmation' => $newPassword,
61+
])
62+
->seeApiSuccess();
63+
64+
$user = App\User::whereEmail($reset->email)->firstOrFail();
65+
$this->assertTrue(Hash::check($newPassword, $user->password));
66+
}
67+
68+
public function testResetPasswordWithTokenUnsuccessfully()
69+
{
70+
$user = factory(App\User::class)->create();
71+
$reset = factory(PasswordReset::class)->create([
72+
'email' => $user->email,
73+
]);
74+
75+
$newPassword = str_random(10);
76+
77+
$this->post('/api/auth/password/reset', [
78+
'email' => $reset->email,
79+
'token' => str_random(10),
80+
'password' => $newPassword,
81+
'password_confirmation' => $newPassword,
82+
])
83+
->seeApiError(422);
84+
}
85+
86+
private function checkEmailContent($checks)
87+
{
88+
$mock = Mockery::mock($this->app['mailer']->getSwiftMailer());
89+
$this->app['mailer']->setSwiftMailer($mock);
90+
91+
$mock->shouldReceive('send')
92+
->withArgs([Mockery::on(function ($message) use ($checks) {
93+
94+
$this->assertEquals($checks['title'], $message->getSubject());
95+
$this->assertSame([$checks['email'] => null], $message->getTo());
96+
$this->assertContains($checks['content'], $message->getBody());
97+
98+
return true;
99+
}), Mockery::any()])
100+
->once();
101+
}
102+
}

0 commit comments

Comments
 (0)