From ca5220db8eb4b89e92625c596ba088ab79f4e1d1 Mon Sep 17 00:00:00 2001 From: alex quiambao Date: Mon, 23 May 2016 16:01:04 +0800 Subject: [PATCH] Added forgot password / reset password module --- CHANGELOG.md | 32 +- .../forgot-password.component.html | 17 + .../forgot-password.component.js | 30 ++ .../forgot-password/forgot-password.less | 3 + .../login-form/login-form.component.html | 6 +- .../login-form/login-form.component.js | 1 + .../password-verify.component.js | 30 +- .../reset-password.component.html | 28 ++ .../reset-password.component.js | 58 ++++ .../reset-password/reset-password.less | 3 + .../user-profile/user-profile.component.js | 105 +++--- .../forgot-password/forgot-password.less | 4 + .../forgot-password/forgot-password.page.html | 10 + .../pages/reset-password/reset-password.less | 4 + .../reset-password/reset-password.page.html | 9 + angular/config/routes.config.js | 29 +- angular/index.components.js | 8 +- .../Auth/PasswordResetController.php | 74 +++++ app/Http/routes.php | 10 + app/PasswordReset.php | 15 + database/factories/ModelFactory.php | 9 +- resources/views/emails/reset_link.blade.php | 300 ++++++++++++++++++ tests/PasswordResetTest.php | 98 ++++++ .../forgot-password.component.spec.js | 23 ++ .../reset-password.component.spec.js | 31 ++ .../components/user-profile.component.spec.js | 35 +- 26 files changed, 885 insertions(+), 87 deletions(-) create mode 100644 angular/app/components/forgot-password/forgot-password.component.html create mode 100644 angular/app/components/forgot-password/forgot-password.component.js create mode 100644 angular/app/components/forgot-password/forgot-password.less create mode 100644 angular/app/components/reset-password/reset-password.component.html create mode 100644 angular/app/components/reset-password/reset-password.component.js create mode 100644 angular/app/components/reset-password/reset-password.less create mode 100644 angular/app/pages/forgot-password/forgot-password.less create mode 100644 angular/app/pages/forgot-password/forgot-password.page.html create mode 100644 angular/app/pages/reset-password/reset-password.less create mode 100644 angular/app/pages/reset-password/reset-password.page.html create mode 100644 app/Http/Controllers/Auth/PasswordResetController.php create mode 100644 app/PasswordReset.php create mode 100644 resources/views/emails/reset_link.blade.php create mode 100644 tests/PasswordResetTest.php create mode 100644 tests/angular/app/components/forgot-password.component.spec.js create mode 100644 tests/angular/app/components/reset-password.component.spec.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 11a5952..8bc5096 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,31 @@ -### v0.0.1 +# Change Log -+ Initial Commit +## [v0.1.1](https://github.com/silverbux/laravel-angular-admin/tree/v0.1.1) (2016-05-23) +[Full Changelog](https://github.com/silverbux/laravel-angular-admin/compare/v0.1.0...v0.1.1) + +**Merged pull requests:** + +- Added forgot password / reset password module [\#18](https://github.com/silverbux/laravel-angular-admin/pull/18) ([silverbux](https://github.com/silverbux)) +- User Profile [\#17](https://github.com/silverbux/laravel-angular-admin/pull/17) ([silverbux](https://github.com/silverbux)) + +## [v0.1.0](https://github.com/silverbux/laravel-angular-admin/tree/v0.1.0) (2016-05-20) +**Merged pull requests:** + +- Added Database Seeders [\#16](https://github.com/silverbux/laravel-angular-admin/pull/16) ([silverbux](https://github.com/silverbux)) +- User email verification [\#15](https://github.com/silverbux/laravel-angular-admin/pull/15) ([silverbux](https://github.com/silverbux)) +- Ability to set role permissions [\#14](https://github.com/silverbux/laravel-angular-admin/pull/14) ([silverbux](https://github.com/silverbux)) +- Updating demo [\#13](https://github.com/silverbux/laravel-angular-admin/pull/13) ([silverbux](https://github.com/silverbux)) +- Users List/ Edit and Role Settings [\#12](https://github.com/silverbux/laravel-angular-admin/pull/12) ([silverbux](https://github.com/silverbux)) +- User Permission Module [\#11](https://github.com/silverbux/laravel-angular-admin/pull/11) ([silverbux](https://github.com/silverbux)) +- Going for standardjs [\#9](https://github.com/silverbux/laravel-angular-admin/pull/9) ([silverbux](https://github.com/silverbux)) +- Access control list [\#8](https://github.com/silverbux/laravel-angular-admin/pull/8) ([silverbux](https://github.com/silverbux)) +- Access control list [\#7](https://github.com/silverbux/laravel-angular-admin/pull/7) ([silverbux](https://github.com/silverbux)) +- Merge pull request \#4 from silverbux/master [\#5](https://github.com/silverbux/laravel-angular-admin/pull/5) ([silverbux](https://github.com/silverbux)) +- production parameter + elixir [\#4](https://github.com/silverbux/laravel-angular-admin/pull/4) ([silverbux](https://github.com/silverbux)) +- Heroku [\#3](https://github.com/silverbux/laravel-angular-admin/pull/3) ([silverbux](https://github.com/silverbux)) +- Heroku Deployment [\#2](https://github.com/silverbux/laravel-angular-admin/pull/2) ([silverbux](https://github.com/silverbux)) +- Heroku [\#1](https://github.com/silverbux/laravel-angular-admin/pull/1) ([silverbux](https://github.com/silverbux)) + + + +\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* \ No newline at end of file diff --git a/angular/app/components/forgot-password/forgot-password.component.html b/angular/app/components/forgot-password/forgot-password.component.html new file mode 100644 index 0000000..271d337 --- /dev/null +++ b/angular/app/components/forgot-password/forgot-password.component.html @@ -0,0 +1,17 @@ +
+
+

Error:

+

Please check your email and try again.

+
+
+ + +
+
+
+
+
+ +
+
+
diff --git a/angular/app/components/forgot-password/forgot-password.component.js b/angular/app/components/forgot-password/forgot-password.component.js new file mode 100644 index 0000000..fc0c832 --- /dev/null +++ b/angular/app/components/forgot-password/forgot-password.component.js @@ -0,0 +1,30 @@ +class ForgotPasswordController { + constructor (API, $state) { + 'ngInject' + + this.API = API + this.$state = $state + this.errorTrigger = false + } + + $onInit () { + this.email = '' + } + + submit () { + this.API.all('auth/password/email').post({ + email: this.email + }).then(() => { + this.$state.go('login', { successMsg: `Please check your email for instructions on how to reset your password.` }) + }, () => { + this.errorTrigger = true + }) + } +} + +export const ForgotPasswordComponent = { + templateUrl: './views/app/components/forgot-password/forgot-password.component.html', + controller: ForgotPasswordController, + controllerAs: 'vm', + bindings: {} +} diff --git a/angular/app/components/forgot-password/forgot-password.less b/angular/app/components/forgot-password/forgot-password.less new file mode 100644 index 0000000..f944094 --- /dev/null +++ b/angular/app/components/forgot-password/forgot-password.less @@ -0,0 +1,3 @@ +.ForgotPassword-input{ + margin-bottom: 0; +} diff --git a/angular/app/components/login-form/login-form.component.html b/angular/app/components/login-form/login-form.component.html index 20a07b6..d931ba9 100644 --- a/angular/app/components/login-form/login-form.component.html +++ b/angular/app/components/login-form/login-form.component.html @@ -11,6 +11,10 @@

Email Unverified

Registration Success!

A verification link has been sent to your Email Account. Thank You!

+
+

Success!

+

{{ vm.successMsg }}

+
@@ -34,6 +38,6 @@

Registration Success!

Sign in using Google Sign in using Facebook
-I forgot my password +I forgot my password
Register a new membership diff --git a/angular/app/components/login-form/login-form.component.js b/angular/app/components/login-form/login-form.component.js index 4e73051..3b410bb 100644 --- a/angular/app/components/login-form/login-form.component.js +++ b/angular/app/components/login-form/login-form.component.js @@ -8,6 +8,7 @@ class LoginFormController { this.AclService = AclService this.registerSuccess = $stateParams.registerSuccess + this.successMsg = $stateParams.successMsg this.loginfailed = false this.unverified = false this.email = '' diff --git a/angular/app/components/password-verify/password-verify.component.js b/angular/app/components/password-verify/password-verify.component.js index 7de37ea..0a5af8b 100644 --- a/angular/app/components/password-verify/password-verify.component.js +++ b/angular/app/components/password-verify/password-verify.component.js @@ -1,33 +1,33 @@ function passwordVerifyClass () { return { - require: "ngModel", + require: 'ngModel', scope: { passwordVerify: '=' }, - link: function(scope, element, attrs, ctrl) { - scope.$watch(function() { - var combined; + link: function (scope, element, attrs, ctrl) { + scope.$watch(function () { + var combined if (scope.passwordVerify || ctrl.$viewValue) { - combined = scope.passwordVerify + '_' + ctrl.$viewValue; + combined = scope.passwordVerify + '_' + ctrl.$viewValue } - return combined; - }, function(value) { + return combined + }, function (value) { if (value) { - ctrl.$parsers.unshift(function(viewValue) { - var origin = scope.passwordVerify; + ctrl.$parsers.unshift(function (viewValue) { + var origin = scope.passwordVerify if (origin !== viewValue) { - ctrl.$setValidity("passwordVerify", false); - return undefined; + ctrl.$setValidity('passwordVerify', false) + return undefined } else { - ctrl.$setValidity("passwordVerify", true); - return viewValue; + ctrl.$setValidity('passwordVerify', true) + return viewValue } - }); + }) } - }); + }) } } } diff --git a/angular/app/components/reset-password/reset-password.component.html b/angular/app/components/reset-password/reset-password.component.html new file mode 100644 index 0000000..b5c81fb --- /dev/null +++ b/angular/app/components/reset-password/reset-password.component.html @@ -0,0 +1,28 @@ +
+
+ +
+
+

{{alert.title}}

+

+
+
+
+ + +
+
+ + +
+ +
+
+
+
+ +
+
+
+
+ diff --git a/angular/app/components/reset-password/reset-password.component.js b/angular/app/components/reset-password/reset-password.component.js new file mode 100644 index 0000000..6eac56b --- /dev/null +++ b/angular/app/components/reset-password/reset-password.component.js @@ -0,0 +1,58 @@ +class ResetPasswordController { + constructor (API, $state) { + 'ngInject' + + this.API = API + this.$state = $state + this.alerts = [] + } + + $onInit () { + this.password = '' + this.password_confirmation = '' + this.isValidToken = false + + this.verifyToken() + } + + verifyToken () { + let email = this.$state.params.email + let token = this.$state.params.token + + this.API.all('auth/password').get('verify', { + email, token}).then(() => { + this.isValidToken = true + }, () => { + this.$state.go('app.landing') + }) + } + + submit () { + this.alerts = [] + let data = { + email: this.$state.params.email, + token: this.$state.params.token, + password: this.password, + password_confirmation: this.password_confirmation + } + + this.API.all('auth/password/reset').post(data).then(() => { + this.$state.go('login', { successMsg: `Your password has been changed, You may now login.` }) + }, (res) => { + let alrtArr = [] + + angular.forEach(res.data.errors, function (value) { + alrtArr = { type: 'error', 'title': 'Error!', msg: value[0]} + }) + + this.alerts.push(alrtArr) + }) + } +} + +export const ResetPasswordComponent = { + templateUrl: './views/app/components/reset-password/reset-password.component.html', + controller: ResetPasswordController, + controllerAs: 'vm', + bindings: {} +} diff --git a/angular/app/components/reset-password/reset-password.less b/angular/app/components/reset-password/reset-password.less new file mode 100644 index 0000000..1afc9a2 --- /dev/null +++ b/angular/app/components/reset-password/reset-password.less @@ -0,0 +1,3 @@ +.ResetPassword-input{ + margin-bottom: 0; +} diff --git a/angular/app/components/user-profile/user-profile.component.js b/angular/app/components/user-profile/user-profile.component.js index 771e3b5..3f914d1 100644 --- a/angular/app/components/user-profile/user-profile.component.js +++ b/angular/app/components/user-profile/user-profile.component.js @@ -1,65 +1,62 @@ -class UserProfileController{ - constructor($stateParams, $state, API){ - 'ngInject'; +class UserProfileController { + constructor ($stateParams, $state, API) { + 'ngInject' - this.$state = $state - this.formSubmitted = false - this.alerts = [] - this.userRolesSelected = [] + this.$state = $state + this.formSubmitted = false + this.alerts = [] + this.userRolesSelected = [] - if ($stateParams.alerts) { - this.alerts.push($stateParams.alerts) - } - - let UserData = API.service('me', API.all('users')) - UserData.one().get() - .then((response) => { - this.userdata = API.copy(response) - this.userdata.data.current_password = '' - this.userdata.data.new_password = '' - this.userdata.data.new_password_confirmation = '' - }) + if ($stateParams.alerts) { + this.alerts.push($stateParams.alerts) } - save (isValid, userForm) { - if (isValid) { - let $state = this.$state - - this.userdata.put() - .then(() => { - let alert = { type: 'success', 'title': 'Success!', msg: 'Profile has been updated.' } - $state.go($state.current, { alerts: alert}) - }, (response) => { - let formErrors = [] - - if(angular.isDefined(response.data.errors.message)) { - formErrors = response.data.errors.message[0]; - } else { - formErrors = response.data.errors; - } - - angular.forEach(formErrors, function(value, key) { - let varkey = key.replace("data.", "") - userForm[varkey].$invalid = true; - userForm[varkey].customError = value[0]; - }); - - this.formSubmitted = true; + let UserData = API.service('me', API.all('users')) + UserData.one().get() + .then((response) => { + this.userdata = API.copy(response) + this.userdata.data.current_password = '' + this.userdata.data.new_password = '' + this.userdata.data.new_password_confirmation = '' + }) + } + + save (isValid, userForm) { + if (isValid) { + let $state = this.$state + + this.userdata.put() + .then(() => { + let alert = { type: 'success', 'title': 'Success!', msg: 'Profile has been updated.' } + $state.go($state.current, { alerts: alert}) + }, (response) => { + let formErrors = [] + + if (angular.isDefined(response.data.errors.message)) { + formErrors = response.data.errors.message[0] + } else { + formErrors = response.data.errors + } + + angular.forEach(formErrors, function (value, key) { + let varkey = key.replace('data.', '') + userForm[varkey].$invalid = true + userForm[varkey].customError = value[0] }) - } else { - this.formSubmitted = true; - } - } - $onInit(){ + this.formSubmitted = true + }) + } else { + this.formSubmitted = true } + } + + $onInit () {} } export const UserProfileComponent = { - templateUrl: './views/app/components/user-profile/user-profile.component.html', - controller: UserProfileController, - controllerAs: 'vm', - bindings: {} + templateUrl: './views/app/components/user-profile/user-profile.component.html', + controller: UserProfileController, + controllerAs: 'vm', + bindings: {} } - - diff --git a/angular/app/pages/forgot-password/forgot-password.less b/angular/app/pages/forgot-password/forgot-password.less new file mode 100644 index 0000000..2885986 --- /dev/null +++ b/angular/app/pages/forgot-password/forgot-password.less @@ -0,0 +1,4 @@ +.ForgotPassword-formContainer{ + margin-top: 80px; + margin-bottom: 80px; +} diff --git a/angular/app/pages/forgot-password/forgot-password.page.html b/angular/app/pages/forgot-password/forgot-password.page.html new file mode 100644 index 0000000..8ff14cb --- /dev/null +++ b/angular/app/pages/forgot-password/forgot-password.page.html @@ -0,0 +1,10 @@ +
+ + +
diff --git a/angular/app/pages/reset-password/reset-password.less b/angular/app/pages/reset-password/reset-password.less new file mode 100644 index 0000000..3e89101 --- /dev/null +++ b/angular/app/pages/reset-password/reset-password.less @@ -0,0 +1,4 @@ +.ResetPassword-formContainer{ + margin-top: 80px; + margin-bottom: 80px; +} diff --git a/angular/app/pages/reset-password/reset-password.page.html b/angular/app/pages/reset-password/reset-password.page.html new file mode 100644 index 0000000..1b37921 --- /dev/null +++ b/angular/app/pages/reset-password/reset-password.page.html @@ -0,0 +1,9 @@ +
+ + +
diff --git a/angular/config/routes.config.js b/angular/config/routes.config.js index 49233f8..d788b5d 100644 --- a/angular/config/routes.config.js +++ b/angular/config/routes.config.js @@ -185,7 +185,8 @@ export function RoutesConfig ($stateProvider, $urlRouterProvider) { bodyClass: 'hold-transition login-page' }, params: { - registerSuccess: null + registerSuccess: null, + successMsg: null } }) .state('loginloader', { @@ -228,6 +229,32 @@ export function RoutesConfig ($stateProvider, $urlRouterProvider) { status: null } }) + .state('forgot_password', { + url: '/forgot-password', + views: { + 'layout': { + templateUrl: getView('forgot-password') + }, + 'header@app': {}, + 'footer@app': {} + }, + data: { + bodyClass: 'hold-transition login-page' + } + }) + .state('reset_password', { + url: '/reset-password/:email/:token', + views: { + 'layout': { + templateUrl: getView('reset-password') + }, + 'header@app': {}, + 'footer@app': {} + }, + data: { + bodyClass: 'hold-transition login-page' + } + }) .state('app.logout', { url: '/logout', views: { diff --git a/angular/index.components.js b/angular/index.components.js index ee7af89..cd5f0d9 100644 --- a/angular/index.components.js +++ b/angular/index.components.js @@ -1,4 +1,4 @@ -import { UserProfileComponent } from './app/components/user-profile/user-profile.component'; +import { UserProfileComponent } from './app/components/user-profile/user-profile.component' import { UserVerificationComponent } from './app/components/user-verification/user-verification.component' import { ComingSoonComponent } from './app/components/coming-soon/coming-soon.component' import { UserEditComponent } from './app/components/user-edit/user-edit.component' @@ -13,11 +13,13 @@ import { DashboardComponent } from './app/components/dashboard/dashboard.compone import { NavSidebarComponent } from './app/components/nav-sidebar/nav-sidebar.component' import { NavHeaderComponent } from './app/components/nav-header/nav-header.component' import { LoginLoaderComponent } from './app/components/login-loader/login-loader.component' +import { ResetPasswordComponent } from './app/components/reset-password/reset-password.component' +import { ForgotPasswordComponent } from './app/components/forgot-password/forgot-password.component' import { LoginFormComponent } from './app/components/login-form/login-form.component' import { RegisterFormComponent } from './app/components/register-form/register-form.component' angular.module('app.components') - .component('userprofile', UserProfileComponent) + .component('userprofile', UserProfileComponent) .component('userVerification', UserVerificationComponent) .component('comingsoon', ComingSoonComponent) .component('useredit', UserEditComponent) @@ -32,5 +34,7 @@ angular.module('app.components') .component('navSidebar', NavSidebarComponent) .component('navHeader', NavHeaderComponent) .component('loginLoader', LoginLoaderComponent) + .component('resetPassword', ResetPasswordComponent) + .component('forgotPassword', ForgotPasswordComponent) .component('loginForm', LoginFormComponent) .component('registerForm', RegisterFormComponent) diff --git a/app/Http/Controllers/Auth/PasswordResetController.php b/app/Http/Controllers/Auth/PasswordResetController.php new file mode 100644 index 0000000..83496df --- /dev/null +++ b/app/Http/Controllers/Auth/PasswordResetController.php @@ -0,0 +1,74 @@ +validate($request, [ + 'email' => 'required|email|exists:users,email', + ]); + + //invalidate old tokens + PasswordReset::whereEmail($request->email)->delete(); + + $email = $request->email; + $reset = PasswordReset::create([ + 'email' => $email, + 'token' => str_random(10), + ]); + + $token = $reset->token; + + Mail::send('emails.reset_link', compact('email', 'token'), function ($mail) use ($email) { + $mail->to($email) + ->from('noreply@example.com') + ->subject('Password reset link'); + }); + + return response()->success(true); + } + + public function verify(Request $request) + { + $this->validate($request, [ + 'email' => 'required|email', + 'token' => 'required', + ]); + + $check = PasswordReset::whereEmail($request->email) + ->whereToken($request->token) + ->first(); + + if (! $check) { + return response()->error('Email does not exist', 422); + } + + return response()->success(true); + } + + public function reset(Request $request) + { + $this->validate($request, [ + 'email' => 'required|email', + 'token' => "required|exists:password_resets,token,email,{$request->email}", + 'password' => 'required|min:8|confirmed', + ]); + + $user = User::whereEmail($request->email)->firstOrFail(); + $user->password = bcrypt($request->password); + $user->save(); + + //delete pending resets + PasswordReset::whereEmail($request->email)->delete(); + + return response()->success(true); + } +} diff --git a/app/Http/routes.php b/app/Http/routes.php index 6f0ffb3..bcdfd0f 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -22,6 +22,16 @@ $api->group(['middleware' => ['api']], function ($api) { $api->controller('auth', 'Auth\AuthController'); + + // Password Reset Routes... + $api->post('auth/password/email', 'Auth\PasswordResetController@sendResetLinkEmail'); + $api->get('auth/password/verify', 'Auth\PasswordResetController@verify'); + $api->post('auth/password/reset', 'Auth\PasswordResetController@reset'); +}); + +$api->group(['middleware' => ['api', 'api.auth']], function ($api) { + $api->get('users/me', 'UserController@getMe'); + $api->put('users/me', 'UserController@putMe'); }); $api->group(['middleware' => ['api', 'api.auth', 'role:admin.super|admin.user']], function ($api) { diff --git a/app/PasswordReset.php b/app/PasswordReset.php new file mode 100644 index 0000000..90c7ae1 --- /dev/null +++ b/app/PasswordReset.php @@ -0,0 +1,15 @@ + $faker->name, 'email' => $faker->email, + 'email_verified' => 1, + 'email_verification_code' => str_random(10), + 'avatar' => 'test', //use bcrypt('password') if you want to assert for a specific password, but it might slow down your tests 'password' => str_random(10), ]; }); -$factory->define(App\Post::class, function (Faker\Generator $faker){ +$factory->define(App\PasswordReset::class, function (Faker\Generator $faker) { return [ - 'name' => $faker->name, - 'topic' => $faker->word, + 'email' => $faker->safeEmail, + 'token' => str_random(10), ]; }); \ No newline at end of file diff --git a/resources/views/emails/reset_link.blade.php b/resources/views/emails/reset_link.blade.php new file mode 100644 index 0000000..88430dc --- /dev/null +++ b/resources/views/emails/reset_link.blade.php @@ -0,0 +1,300 @@ + + + + + + Welcome Email + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ +
+ + + + + +
+ logo + + twitter + facebook + rss +
+
+ +
+
+
+
+ + + + + + + + + + +
+ Reset Password +
+ Please click the link below to reset your password. +
+ +
+
+
+
+ + + + +
+ Awesome Inc
+ 1234 Awesome St
+ Wonderland

+
+
+
+ + diff --git a/tests/PasswordResetTest.php b/tests/PasswordResetTest.php new file mode 100644 index 0000000..4518828 --- /dev/null +++ b/tests/PasswordResetTest.php @@ -0,0 +1,98 @@ +create(); + + $this->checkEmailContent([ + 'title' => 'Password reset link', + 'email' => $user->email, + 'content' => 'Reset Password', //contains + ]); + + $this->post('/api/auth/password/email', ['email' => $user->email]) + ->seeApiSuccess(); + } + + public function testVerifyTokenSuccessfully() + { + $reset = factory(PasswordReset::class)->create(); + + $this->get("/api/auth/password/verify?email={$reset->email}&token={$reset->token}") + ->seeApiSuccess(); + } + + public function testVerifyTokenUnsuccessfully() + { + $reset = factory(PasswordReset::class)->create(); + + $this->get('/api/auth/password/verify', [ + 'email' => $reset->email, + 'token' => str_random(10), + ]) + ->seeValidationError(); + } + + public function testResetPasswordWithTokenSuccessfully() + { + $user = factory(App\User::class)->create(); + $reset = factory(PasswordReset::class)->create([ + 'email' => $user->email, + ]); + + $newPassword = str_random(10); + + $this->post('/api/auth/password/reset', [ + 'email' => $reset->email, + 'token' => $reset->token, + 'password' => $newPassword, + 'password_confirmation' => $newPassword, + ]) + ->seeApiSuccess(); + + $user = App\User::whereEmail($reset->email)->firstOrFail(); + $this->assertTrue(Hash::check($newPassword, $user->password)); + } + + public function testResetPasswordWithTokenUnsuccessfully() + { + $user = factory(App\User::class)->create(); + $reset = factory(PasswordReset::class)->create([ + 'email' => $user->email, + ]); + + $newPassword = str_random(10); + + $this->post('/api/auth/password/reset', [ + 'email' => $reset->email, + 'token' => str_random(10), + 'password' => $newPassword, + 'password_confirmation' => $newPassword, + ]) + ->seeApiError(422); + } + + private function checkEmailContent($checks) + { + $mock = Mockery::mock($this->app['mailer']->getSwiftMailer()); + $this->app['mailer']->setSwiftMailer($mock); + + $mock->shouldReceive('send') + ->withArgs([Mockery::on(function ($message) use ($checks) { + + $this->assertEquals($checks['title'], $message->getSubject()); + $this->assertSame([$checks['email'] => null], $message->getTo()); + $this->assertContains($checks['content'], $message->getBody()); + + return true; + }), Mockery::any()]) + ->once(); + } +} diff --git a/tests/angular/app/components/forgot-password.component.spec.js b/tests/angular/app/components/forgot-password.component.spec.js new file mode 100644 index 0000000..071968f --- /dev/null +++ b/tests/angular/app/components/forgot-password.component.spec.js @@ -0,0 +1,23 @@ +ngDescribe({ + name: 'Test forgot-password component', + modules: 'app', + inject: '$http', + element: '', + http: { + post: { + '/api/auth/password/email': { + data: true + } + } + }, + tests: function (deps) { + it('should request email verification successfully', () => { + var component = deps.element.isolateScope().vm + + component.email = 'email@localhost.com' + component.submit() + + deps.http.flush() + }) + } +}) diff --git a/tests/angular/app/components/reset-password.component.spec.js b/tests/angular/app/components/reset-password.component.spec.js new file mode 100644 index 0000000..1e16513 --- /dev/null +++ b/tests/angular/app/components/reset-password.component.spec.js @@ -0,0 +1,31 @@ +ngDescribe({ + name: 'Test reset-password component', + modules: 'app', + inject: '$http', + element: '', + http: { + get: { + '/api/auth/password/verify': { + data: true + } + }, + post: { + '/api/auth/password/reset': { + data: true + } + } + }, + tests: function (deps) { + it('should expect verification on init', () => { + // + }) + + it('should submit password reset successfully', () => { + var component = deps.element.isolateScope().vm + + component.submit() + + deps.http.flush() + }) + } +}) diff --git a/tests/angular/app/components/user-profile.component.spec.js b/tests/angular/app/components/user-profile.component.spec.js index c457fc7..821aeef 100644 --- a/tests/angular/app/components/user-profile.component.spec.js +++ b/tests/angular/app/components/user-profile.component.spec.js @@ -1,11 +1,28 @@ ngDescribe({ - name: 'Test user-profile component', - modules: 'app', - element: '', - tests: function (deps) { - - it('basic test', () => { - // - }); + name: 'Test user-profile component', + modules: 'app', + element: '', + http: { + get: { + '/api/users/me': { + data: true + } } -}); + }, + tests: function (deps) { + it('Should have name, email, password inputs', () => { + var inputs = deps.element.find('input') + expect(inputs.length).toBe(5) + var name = deps.element.find('input')[0] + expect(name.attributes['type'].value).toBe('text') + var email = deps.element.find('input')[1] + expect(email.attributes['type'].value).toBe('email') + var passsword = deps.element.find('input')[2] + expect(passsword.attributes['type'].value).toBe('password') + var passsword = deps.element.find('input')[3] + expect(passsword.attributes['type'].value).toBe('password') + var passsword = deps.element.find('input')[4] + expect(passsword.attributes['type'].value).toBe('password') + }) + } +})