Commit a05a7c31aa91e0ccbd094fe75b09fd7cfe576b97

Authored by Daniela Feitosa
1 parent 33647b98
Exists in forgot_password

Ticket #107 Implementation of new password generation

After clicking on "Forgot your password?", the user receives an email
with a link to create a new password.
src/app/login/new-password.component.spec.ts 0 → 100644
@@ -0,0 +1,87 @@ @@ -0,0 +1,87 @@
  1 +import { ComponentTestHelper, createClass } from "../../spec/component-test-helper";
  2 +import * as helpers from "../../spec/helpers";
  3 +import { PasswordComponent } from "./new-password.component";
  4 +
  5 +
  6 +describe("Password Component", () => {
  7 + const htmlTemplate: string = '<new-password></new-password>';
  8 +
  9 + let helper: ComponentTestHelper<PasswordComponent>;
  10 + let passwordService = helpers.mocks.passwordService;
  11 + let stateService = jasmine.createSpyObj("$state", ["transitionTo"]);
  12 + let stateParams = jasmine.createSpyObj("$stateParams", ["code"]);
  13 + let notificationService = helpers.mocks.notificationService;
  14 + notificationService.success = jasmine.createSpy('success');
  15 + notificationService.error = jasmine.createSpy('error');
  16 +
  17 +
  18 + let data: any;
  19 +
  20 + beforeEach(() => {
  21 + angular.mock.module('templates');
  22 + angular.mock.module('ngSanitize');
  23 + angular.mock.module('ngMessages');
  24 + angular.mock.module('ngPassword');
  25 + });
  26 +
  27 + beforeEach((done) => {
  28 + let cls = createClass({
  29 + template: htmlTemplate,
  30 + directives: [PasswordComponent],
  31 + providers: [
  32 + helpers.createProviderToValue('$state', stateService),
  33 + helpers.createProviderToValue('$stateParams', stateParams),
  34 + helpers.createProviderToValue('PasswordService', passwordService),
  35 + helpers.createProviderToValue('NotificationService', notificationService),
  36 + ]
  37 + });
  38 + helper = new ComponentTestHelper<PasswordComponent>(cls, done);
  39 + });
  40 +
  41 + it('new password page was rendered', () => {
  42 + expect(helper.debugElement.query('div.new-password-page').length).toEqual(1);
  43 + });
  44 +
  45 + it("changes the user password", done => {
  46 + data = {
  47 + code: '1234567890',
  48 + password: 'test',
  49 + passwordConfirmation: 'test'
  50 + };
  51 +
  52 + helper.component.code = data.code;
  53 + helper.component.password = data.password;
  54 + helper.component.passwordConfirmation = data.passwordConfirmation;
  55 +
  56 + passwordService.new_password = jasmine.createSpy("new_password").and.returnValue(Promise.resolve());
  57 +
  58 + helper.component.sendNewPassword();
  59 + expect(passwordService.new_password).toHaveBeenCalledWith('1234567890', 'test', 'test');
  60 +
  61 + expect(notificationService.success).toHaveBeenCalled();
  62 +
  63 + done();
  64 + });
  65 +
  66 + it("fails when try to change the user password", done => {
  67 + data = {
  68 + code: '1234567890',
  69 + password: 'test',
  70 + passwordConfirmation: 'test-invalid'
  71 + };
  72 +
  73 + helper.component.code = data.code;
  74 + helper.component.password = data.password;
  75 + helper.component.passwordConfirmation = data.passwordConfirmation;
  76 +
  77 + passwordService.new_password = jasmine.createSpy("new_password").and.returnValue(Promise.reject({data: {message: 'Error'}}));
  78 +
  79 + helper.component.sendNewPassword();
  80 + expect(passwordService.new_password).toHaveBeenCalledWith('1234567890', 'test', 'test-invalid');
  81 +
  82 + expect(notificationService.error).toHaveBeenCalled();
  83 +
  84 + done();
  85 + });
  86 +
  87 +});
src/app/login/new-password.component.ts 0 → 100644
@@ -0,0 +1,36 @@ @@ -0,0 +1,36 @@
  1 +import { StateConfig, Component, Inject, provide } from 'ng-forward';
  2 +
  3 +import { PasswordService } from "../../lib/ng-noosfero-api/http/password.service";
  4 +import { NotificationService } from "./../shared/services/notification.service";
  5 +import { AuthController } from "./auth.controller";
  6 +
  7 +@Component({
  8 + selector: 'new-password',
  9 + templateUrl: 'app/login/new-password.html',
  10 + providers: [provide('passwordService', { useClass: PasswordService })]
  11 +})
  12 +@Inject(PasswordService, "$state", "$stateParams", NotificationService)
  13 +export class PasswordComponent {
  14 +
  15 + code: string;
  16 + password: string;
  17 + passwordConfirmation: string;
  18 +
  19 + constructor(
  20 + public passwordService: PasswordService,
  21 + private $state: ng.ui.IStateService,
  22 + private $stateParams: ng.ui.IStateParamsService,
  23 + private notificationService: NotificationService) {
  24 +
  25 + this.code = this.$stateParams['code'];
  26 + }
  27 +
  28 + sendNewPassword() {
  29 + this.passwordService.new_password(this.code, this.password, this.passwordConfirmation).then((response) => {
  30 + this.notificationService.success({ title: "newPasswd.success.title", message: "newPasswd.success.message", timer: 5000 });
  31 + this.$state.transitionTo('main.environment');
  32 + }).catch((response) => {
  33 + this.notificationService.error({ title: "newPasswd.failed.title", message: "newPasswd.failed.message" });
  34 + });
  35 + }
  36 +}
src/app/login/new-password.html 0 → 100644
@@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
  1 +<div class="new-password-page col-xs-4 col-xs-offset-4">
  2 + <h1>{{"new_password.welcomeMessageTitle" | translate }}</h1>
  3 +
  4 + <form name="newPasswdForm">
  5 + <div class="form-group" ng-class="{'has-error': newPasswdForm.password.$invalid && newPasswdForm.password.$touched }">
  6 + <div class="input-group">
  7 + <span class="input-group-addon"><i class="fa fa-lock"></i></span>
  8 + <input type="password" class="form-control" id="password" name="password" placeholder="{{'account.register.passwordLabel' | translate }}" ng-model="vm.password" required minlength="4">
  9 + </div>
  10 + <div class="help-block" ng-show="newPasswdForm.password.$touched" ng-messages="newPasswdForm.password.$error">
  11 + <ul class="list-unstyled">
  12 + <li ng-messages-include="app/shared/messages/form-errors.html"></li>
  13 + </ul>
  14 + </div>
  15 + </div>
  16 +
  17 + <div class="form-group" ng-class="{'has-error': newPasswdForm.passwordConfirm.$invalid && newPasswdForm.passwordConfirm.$touched }">
  18 + <div class="input-group">
  19 + <span class="input-group-addon"><i class="fa fa-unlock-alt"></i></span>
  20 + <input type="password" class="form-control" id="passwordConfirm" name="passwordConfirm" match-password="password" placeholder="{{'account.register.passwordConfirmationLabel' | translate}}" ng-model="vm.passwordConfirmation" required>
  21 + </div>
  22 + <div class="help-block" ng-show="newPasswdForm.passwordConfirm.$touched" ng-messages="newPasswdForm.passwordConfirm.$error">
  23 + <ul class="list-unstyled">
  24 + <li ng-messages-include="app/shared/messages/form-errors.html"></li>
  25 + <li ng-message="passwordMatch" translate="messages.invalid.passwordMatch"></li>
  26 + </ul>
  27 + </div>
  28 + </div>
  29 + <button type="submit" class="btn btn-default btn-block" ng-click="vm.sendNewPassword()" ng-disabled="newPasswdForm.$invalid">{{"newPasswdForm.submit" | translate}}</button>
  30 + </form>
  31 +</div>
src/app/main/main.component.ts
@@ -4,6 +4,7 @@ import { ArticleBlogComponent } from &quot;./../article/types/blog/blog.component&quot;; @@ -4,6 +4,7 @@ import { ArticleBlogComponent } from &quot;./../article/types/blog/blog.component&quot;;
4 4
5 import { ArticleViewComponent } from "./../article/article-default-view.component"; 5 import { ArticleViewComponent } from "./../article/article-default-view.component";
6 6
  7 +import { PasswordComponent } from "../login/new-password.component";
7 import { ProfileComponent } from "../profile/profile.component"; 8 import { ProfileComponent } from "../profile/profile.component";
8 import { BoxesComponent } from "../layout/boxes/boxes.component"; 9 import { BoxesComponent } from "../layout/boxes/boxes.component";
9 import { BlockContentComponent } from "../layout/blocks/block-content.component"; 10 import { BlockContentComponent } from "../layout/blocks/block-content.component";
@@ -120,7 +121,8 @@ export class EnvironmentContent { @@ -120,7 +121,8 @@ export class EnvironmentContent {
120 MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent, StatisticsBlockComponent, 121 MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent, StatisticsBlockComponent,
121 LoginBlockComponent, CustomContentComponent, PermissionDirective, SearchFormComponent, SearchComponent, 122 LoginBlockComponent, CustomContentComponent, PermissionDirective, SearchFormComponent, SearchComponent,
122 PersonTagsPluginInterestsBlockComponent, TagsBlockComponent, RecentActivitiesPluginActivitiesBlockComponent, 123 PersonTagsPluginInterestsBlockComponent, TagsBlockComponent, RecentActivitiesPluginActivitiesBlockComponent,
123 - ProfileImagesPluginProfileImagesBlockComponent, BlockComponent, RegisterComponent, TasksMenuComponent, TaskListComponent 124 + ProfileImagesPluginProfileImagesBlockComponent, BlockComponent, RegisterComponent, TasksMenuComponent, TaskListComponent,
  125 + PasswordComponent
124 ].concat(plugins.mainComponents).concat(plugins.hotspots), 126 ].concat(plugins.mainComponents).concat(plugins.hotspots),
125 providers: [AuthService, SessionService, NotificationService, BodyStateClassesService, 127 providers: [AuthService, SessionService, NotificationService, BodyStateClassesService,
126 "ngAnimate", "ngCookies", "ngStorage", "ngTouch", 128 "ngAnimate", "ngCookies", "ngStorage", "ngTouch",
@@ -172,6 +174,18 @@ export class EnvironmentContent { @@ -172,6 +174,18 @@ export class EnvironmentContent {
172 } 174 }
173 }, 175 },
174 { 176 {
  177 + url: "/account/new_password/:code",
  178 + component: PasswordComponent,
  179 + name: 'main.newPasswd',
  180 + views: {
  181 + "content": {
  182 + templateUrl: "app/login/new-password.html",
  183 + controller: PasswordComponent,
  184 + controllerAs: "vm"
  185 + }
  186 + }
  187 + },
  188 + {
175 url: "^/:profile", 189 url: "^/:profile",
176 abstract: true, 190 abstract: true,
177 component: ProfileComponent, 191 component: ProfileComponent,
src/lib/ng-noosfero-api/http/password.service.spec.ts 0 → 100644
@@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
  1 +import { PasswordService } from "./password.service";
  2 +
  3 +describe("Services", () => {
  4 +
  5 + describe("Password Service", () => {
  6 +
  7 + let $httpBackend: ng.IHttpBackendService;
  8 + let passwordService: PasswordService;
  9 + let $rootScope: ng.IRootScopeService;
  10 + let data: any;
  11 +
  12 + beforeEach(angular.mock.module("main", ($translateProvider: angular.translate.ITranslateProvider) => {
  13 + $translateProvider.translations('en', {});
  14 + }));
  15 +
  16 + beforeEach(inject((_$httpBackend_: ng.IHttpBackendService,
  17 +_PasswordService_: PasswordService, _$rootScope_: ng.IRootScopeService) => {
  18 + $httpBackend = _$httpBackend_;
  19 + passwordService = _PasswordService_;
  20 + $rootScope = _$rootScope_;
  21 + }));
  22 +
  23 + describe("Succesfull request", () => {
  24 +
  25 + it("should change user password", (done) => {
  26 + data = {
  27 + code: '1234567890',
  28 + password: 'test',
  29 + password_confirmation: 'test'
  30 + };
  31 +
  32 + $httpBackend.expectPATCH(`/api/v1/new_password?code=${data.code}&password=${data.password}&password_confirmation=${data.password_confirmation}`).respond(201, [{ login: "test" }]);
  33 + passwordService.new_password('1234567890', 'test', 'test').then((response: restangular.IResponse) => {
  34 + expect(response.data[0].login).toEqual("test");
  35 + done();
  36 + });
  37 + $httpBackend.flush();
  38 + });
  39 + });
  40 + });
  41 +});
src/lib/ng-noosfero-api/http/password.service.ts 0 → 100644
@@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
  1 +import { Injectable, Inject } from "ng-forward";
  2 +import { RestangularService } from "./restangular_service";
  3 +
  4 +@Injectable()
  5 +@Inject("Restangular")
  6 +export class PasswordService {
  7 + constructor(private Restangular: restangular.IService) {
  8 + this.Restangular = Restangular;
  9 + }
  10 +
  11 + new_password(code: string, password: string, password_confirmation: string): ng.IPromise<noosfero.RestResult<noosfero.User>> {
  12 + return this.Restangular.all("").customOperation("patch", "new_password", {code: code, password: password, password_confirmation: password_confirmation });
  13 + }
  14 +}
src/spec/mocks.ts
@@ -257,6 +257,11 @@ export var mocks: any = { @@ -257,6 +257,11 @@ export var mocks: any = {
257 changeLanguage: (lang: string) => { }, 257 changeLanguage: (lang: string) => { },
258 translate: (text: string) => { return text; } 258 translate: (text: string) => { return text; }
259 }, 259 },
  260 + passwordService: {
  261 + new_password: (param: any) => {
  262 + return Promise.resolve({ status: 201 });
  263 + }
  264 + },
260 notificationService: { 265 notificationService: {
261 success: () => { }, 266 success: () => { },
262 confirmation: () => { }, 267 confirmation: () => { },