Commit 33647b984dd508f4f2ec493305971379222e0de3

Authored by Daniela Feitosa
1 parent afb4744a
Exists in forgot_password

Ticket #107 Implementation of 'Forgot password'

src/app/login/auth.controller.spec.ts
1 import {AuthController} from "./auth.controller"; 1 import {AuthController} from "./auth.controller";
2 import {AuthService} from "./auth.service"; 2 import {AuthService} from "./auth.service";
  3 +import * as helpers from "./../../spec/helpers";
3 4
4 describe("Controllers", () => { 5 describe("Controllers", () => {
5 6
6 7
7 describe("AuthController", () => { 8 describe("AuthController", () => {
8 9
9 - it("calls authenticate on AuthService when login called", () => { 10 + let $modal: any;
  11 + let notificationService = helpers.mocks.notificationService;
  12 + notificationService.success = jasmine.createSpy('info');
  13 + notificationService.error = jasmine.createSpy('error');
10 14
11 - // creating a Mock AuthService  
12 - let AuthServiceMock: AuthService = jasmine.createSpyObj("AuthService", ["login"]); 15 + let authController: any;
  16 + let AuthServiceMock: AuthService;
  17 + let username: string;
13 18
14 - // pass AuthServiceMock into the constructor  
15 - let authController = new AuthController(null, null, AuthServiceMock); 19 + beforeEach(() => {
  20 + $modal = helpers.mocks.$modal;
  21 + AuthServiceMock = jasmine.createSpyObj("AuthService", ["login",
  22 +"forgotPassword"]);
  23 + authController = new AuthController(null, null, AuthServiceMock, $modal, notificationService);
  24 + });
16 25
17 - // setup of authController -> set the credentials instance property  
18 - let credentials = { username: "username", password: "password" };  
19 26
  27 + it("calls authenticate on AuthService when login called", () => {
  28 + let credentials = { username: "username", password: "password" };
20 authController.credentials = credentials; 29 authController.credentials = credentials;
21 -  
22 - // calls the authController login method  
23 authController.login(); 30 authController.login();
24 -  
25 - // checks if the method login of the injected AuthService has been called  
26 expect(AuthServiceMock.login).toHaveBeenCalledWith(credentials); 31 expect(AuthServiceMock.login).toHaveBeenCalledWith(credentials);
27 -  
28 }); 32 });
29 33
  34 + it('should open forgot password on click', (done: Function) => {
  35 + spyOn($modal, "open");
  36 + authController.openForgotPassword();
  37 + expect($modal.open).toHaveBeenCalled();
  38 + expect($modal.open).toHaveBeenCalledWith({
  39 + templateUrl: 'app/login/forgot-password.html',
  40 + controller: AuthController,
  41 + controllerAs: 'vm',
  42 + bindToController: true
  43 + });
  44 + done();
  45 + });
30 46
  47 + it("calls forgotPassword on AuthService when sendPasswdInfo called", done => {
  48 + authController.username = "john";
  49 + AuthServiceMock.forgotPassword = jasmine.createSpy("forgotPassword").and.returnValue(Promise.resolve());
  50 + authController.sendPasswdInfo();
  51 + expect(AuthServiceMock.forgotPassword).toHaveBeenCalledWith("john");
  52 + done();
  53 + });
31 54
32 }); 55 });
33 }); 56 });
src/app/login/auth.controller.ts
1 -import {AuthService} from "./auth.service"; 1 +import { AuthService } from "./auth.service";
  2 +import { NotificationService } from "./../shared/services/notification.service";
2 3
3 export class AuthController { 4 export class AuthController {
4 5
5 - static $inject = ["$log", "$stateParams", "AuthService"]; 6 + static $inject = ["$log", "$stateParams", "AuthService", "$uibModal", "NotificationService"];
  7 + modalInstance: ng.ui.bootstrap.IModalServiceInstance;
6 8
7 constructor( 9 constructor(
8 private $log: ng.ILogService, 10 private $log: ng.ILogService,
9 private $stateParams: any, 11 private $stateParams: any,
10 - private AuthService: AuthService 12 + private AuthService: AuthService,
  13 + private $uibModal: ng.ui.bootstrap.IModalService,
  14 + private notificationService: NotificationService
11 ) { 15 ) {
12 16
13 } 17 }
14 18
15 credentials: noosfero.Credentials; 19 credentials: noosfero.Credentials;
  20 + username: string;
16 21
17 login() { 22 login() {
18 this.AuthService.login(this.credentials); 23 this.AuthService.login(this.credentials);
19 } 24 }
  25 +
  26 + openForgotPassword() {
  27 + this.modalInstance = this.$uibModal.open({
  28 + templateUrl: 'app/login/forgot-password.html',
  29 + controller: AuthController,
  30 + controllerAs: 'vm',
  31 + bindToController: true,
  32 + });
  33 + }
  34 +
  35 + sendPasswdInfo() {
  36 + this.AuthService.forgotPassword(this.username).then((response) => {
  37 + this.notificationService.info({ title: "forgotPasswd.email_sent.title.info", message: "forgotPasswd.email_sent.message.info" });
  38 + }).catch((response) => {
  39 + this.notificationService.error({ title: "forgotPasswd.not_found.title.error", message: "forgotPasswd.not_found.message.error" });
  40 + this.openForgotPassword();
  41 + });
  42 + }
20 } 43 }
src/app/login/auth.service.ts
@@ -2,11 +2,12 @@ import {Injectable, Inject, EventEmitter} from "ng-forward"; @@ -2,11 +2,12 @@ import {Injectable, Inject, EventEmitter} from "ng-forward";
2 2
3 import {NoosferoRootScope, UserResponse} from "./../shared/models/interfaces"; 3 import {NoosferoRootScope, UserResponse} from "./../shared/models/interfaces";
4 import {SessionService} from "./session.service"; 4 import {SessionService} from "./session.service";
  5 +import { RestangularService } from "./../../lib/ng-noosfero-api/http/restangular_service";
5 6
6 import {AuthEvents} from "./auth-events"; 7 import {AuthEvents} from "./auth-events";
7 8
8 @Injectable() 9 @Injectable()
9 -@Inject("$http", SessionService, "$log") 10 +@Inject("$http", SessionService, "$log", "Restangular")
10 export class AuthService { 11 export class AuthService {
11 12
12 public loginSuccess: EventEmitter<noosfero.User> = new EventEmitter<noosfero.User>(); 13 public loginSuccess: EventEmitter<noosfero.User> = new EventEmitter<noosfero.User>();
@@ -15,7 +16,10 @@ export class AuthService { @@ -15,7 +16,10 @@ export class AuthService {
15 16
16 constructor(private $http: ng.IHttpService, 17 constructor(private $http: ng.IHttpService,
17 private sessionService: SessionService, 18 private sessionService: SessionService,
18 - private $log: ng.ILogService) { 19 + private $log: ng.ILogService,
  20 + private Restangular: restangular.IService
  21 + ) {
  22 + this.Restangular = Restangular;
19 } 23 }
20 24
21 loginFromCookie() { 25 loginFromCookie() {
@@ -76,4 +80,9 @@ export class AuthService { @@ -76,4 +80,9 @@ export class AuthService {
76 throw new Error(`The event: ${eventName} not exists`); 80 throw new Error(`The event: ${eventName} not exists`);
77 } 81 }
78 } 82 }
  83 +
  84 + forgotPassword(value: string): ng.IPromise<noosfero.RestResult<any>> {
  85 + return this.Restangular.all("").customPOST("", "forgot_password", {value: value});
  86 + }
  87 +
79 } 88 }
src/app/login/forgot-password.html 0 → 100644
@@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
  1 +<div class="modal-header">
  2 + <h3 class="modal-title">{{"auth.form.forgot_passwd" | translate}}</h3>
  3 +</div>
  4 +<div class="modal-body">
  5 + <form name="forgotPasswdForm">
  6 + <div class="form-group" ng-class="{'has-error': forgotPasswdForm.username.$error.required }">
  7 + <label for="forgotPasswdLogin">{{"auth.form.login" | translate}}</label>
  8 + <input type="text" name="username" class="form-control" id="forgotPasswdLogin" placeholder="{{'auth.form.login' | translate}}" ng-model="vm.username" required>
  9 + <div class="help-block" ng-messages="forgotPasswdForm.username.$error">
  10 + <div ng-if="forgotPasswdForm.username.$touched">
  11 + <ul class="list-unstyled">
  12 + <li ng-messages-include="app/shared/messages/form-errors.html"></li>
  13 + </ul>
  14 + </div>
  15 + </div>
  16 + </div>
  17 + <button type="submit" class="btn btn-default btn-block" ng-click="vm.sendPasswdInfo(); $close()" ng-disabled="forgotPasswdForm.$invalid">{{"forgotPasswd.send_instructions_button" | translate}}</button>
  18 + </form>
  19 + <p>{{"forgotPasswd.info" | translate}}</p>
  20 +</div>
src/app/login/login.html
@@ -18,6 +18,9 @@ @@ -18,6 +18,9 @@
18 {{"auth.form.keepLoggedIn" | translate}} 18 {{"auth.form.keepLoggedIn" | translate}}
19 </label> 19 </label>
20 </div> 20 </div>
  21 + <div class="pull-right">
  22 + <a ng-click="vm.openForgotPassword()" href="">{{"auth.form.forgot_passwd" | translate}}</a>
  23 + </div>
21 </div> 24 </div>
22 <button type="submit" class="btn btn-default btn-block" ng-click="vm.login()">{{"auth.form.login_button" | translate}}</button> 25 <button type="submit" class="btn btn-default btn-block" ng-click="vm.login()">{{"auth.form.login_button" | translate}}</button>
23 </form> 26 </form>
src/app/login/login.scss
@@ -10,6 +10,12 @@ @@ -10,6 +10,12 @@
10 margin-top: 20px; 10 margin-top: 20px;
11 text-transform: uppercase; 11 text-transform: uppercase;
12 } 12 }
  13 + .form-inline {
  14 + display: inline;
  15 + div {
  16 + display: inline-block;
  17 + }
  18 + }
13 } 19 }
14 } 20 }
15 21
src/app/shared/services/notification.service.ts
@@ -14,6 +14,8 @@ export class NotificationService { @@ -14,6 +14,8 @@ export class NotificationService {
14 public static DEFAULT_ERROR_TITLE = "notification.error.default.title"; 14 public static DEFAULT_ERROR_TITLE = "notification.error.default.title";
15 public static DEFAULT_ERROR_MESSAGE = "notification.error.default.message"; 15 public static DEFAULT_ERROR_MESSAGE = "notification.error.default.message";
16 public static DEFAULT_SUCCESS_TIMER = 1000; 16 public static DEFAULT_SUCCESS_TIMER = 1000;
  17 + public static DEFAULT_INFO_TITLE = "notification.info.default.title";
  18 + public static DEFAULT_INFO_MESSAGE = "notification.info.default.message";
17 19
18 error({ 20 error({
19 message = NotificationService.DEFAULT_ERROR_MESSAGE, 21 message = NotificationService.DEFAULT_ERROR_MESSAGE,
@@ -40,6 +42,14 @@ export class NotificationService { @@ -40,6 +42,14 @@ export class NotificationService {
40 this.showMessage({ title: title, text: message, showCancelButton: showCancelButton, type: type, closeOnConfirm: false }, confirmationFunction); 42 this.showMessage({ title: title, text: message, showCancelButton: showCancelButton, type: type, closeOnConfirm: false }, confirmationFunction);
41 } 43 }
42 44
  45 + info({
  46 + message = NotificationService.DEFAULT_INFO_MESSAGE,
  47 + title = NotificationService.DEFAULT_INFO_TITLE,
  48 + showConfirmButton = true
  49 + } = {}) {
  50 + this.showMessage({ title: title, text: message, showConfirmButton: showConfirmButton, type: "info" });
  51 + }
  52 +
43 private showMessage({title, text, type = "success", timer = null, showConfirmButton = true, showCancelButton = false, closeOnConfirm = true}, confirmationFunction: Function = null) { 53 private showMessage({title, text, type = "success", timer = null, showConfirmButton = true, showCancelButton = false, closeOnConfirm = true}, confirmationFunction: Function = null) {
44 this.$log.debug("Notification message:", title, text, type, this.translatorService.currentLanguage()); 54 this.$log.debug("Notification message:", title, text, type, this.translatorService.currentLanguage());
45 this.SweetAlert.swal({ 55 this.SweetAlert.swal({
src/languages/en.json
@@ -34,8 +34,15 @@ @@ -34,8 +34,15 @@
34 "auth.form.login": "Username or Email address", 34 "auth.form.login": "Username or Email address",
35 "auth.form.password": "Password", 35 "auth.form.password": "Password",
36 "auth.form.keepLoggedIn": "Keep me logged in", 36 "auth.form.keepLoggedIn": "Keep me logged in",
  37 + "auth.form.forgot_passwd": "Forgot your password?",
37 "auth.form.login_button": "Login", 38 "auth.form.login_button": "Login",
38 "auth.createAccount": "Create account", 39 "auth.createAccount": "Create account",
  40 + "forgotPasswd.send_instructions_button": "Send instructions",
  41 + "forgotPasswd.info": "Upon clicking on the button above, you'll receive an email with instructions on how to create a new password.",
  42 + "forgotPasswd.email_sent.title.info": "Change your password",
  43 + "forgotPasswd.email_sent.message.info": "Follow the instructions sent by email to change your password",
  44 + "forgotPasswd.not_found.title.error": "User not found",
  45 + "forgotPasswd.not_found.message.error": "Could not send password recovery for the user",
39 "navbar.content_viewer_actions.new_item": "New Item", 46 "navbar.content_viewer_actions.new_item": "New Item",
40 "navbar.profile_actions.new_item": "New Item", 47 "navbar.profile_actions.new_item": "New Item",
41 "navbar.content_viewer_actions.new_post": "New Post", 48 "navbar.content_viewer_actions.new_post": "New Post",
@@ -43,6 +50,8 @@ @@ -43,6 +50,8 @@
43 "navbar.profile_actions.new_discussion": "New Discussion", 50 "navbar.profile_actions.new_discussion": "New Discussion",
44 "notification.error.default.message": "Something went wrong!", 51 "notification.error.default.message": "Something went wrong!",
45 "notification.error.default.title": "Oops...", 52 "notification.error.default.title": "Oops...",
  53 + "notification.info.default.message": "",
  54 + "notification.info.default.title": "Information",
46 "notification.profile.not_found": "Page not found", 55 "notification.profile.not_found": "Page not found",
47 "notification.http_error.401.message": "Unauthorized", 56 "notification.http_error.401.message": "Unauthorized",
48 "notification.http_error.500.message": "Server error", 57 "notification.http_error.500.message": "Server error",
src/languages/pt.json
@@ -34,8 +34,15 @@ @@ -34,8 +34,15 @@
34 "auth.form.login": "Nome de usuário ou Email", 34 "auth.form.login": "Nome de usuário ou Email",
35 "auth.form.password": "Senha", 35 "auth.form.password": "Senha",
36 "auth.form.keepLoggedIn": "Continuar logado", 36 "auth.form.keepLoggedIn": "Continuar logado",
  37 + "auth.form.forgot_passwd": "Esqueceu sua senha?",
37 "auth.form.login_button": "Login", 38 "auth.form.login_button": "Login",
38 "auth.createAccount": "Criar conta", 39 "auth.createAccount": "Criar conta",
  40 + "forgotPasswd.send_instructions_button": "Send instructions",
  41 + "forgotPasswd.info": "Ao clicar no botão acima, você receberá um email com instruções para criar uma nova senha.",
  42 + "forgotPasswd.email_sent.title.info": "Altere sua senha",
  43 + "forgotPasswd.email_sent.message.info": "Siga as instruções enviadas por e-mail para alterar sua senha",
  44 + "forgotPasswd.not_found.title.error": "Usuário não encontrado",
  45 + "forgotPasswd.not_found.message.error": "Não foi possível recuperar a senha deste usuário",
39 "navbar.content_viewer_actions.new_item": "Novo Item", 46 "navbar.content_viewer_actions.new_item": "Novo Item",
40 "navbar.profile_actions.new_item": "Novo Item", 47 "navbar.profile_actions.new_item": "Novo Item",
41 "navbar.content_viewer_actions.new_post": "Novo Post", 48 "navbar.content_viewer_actions.new_post": "Novo Post",
@@ -43,6 +50,8 @@ @@ -43,6 +50,8 @@
43 "navbar.profile_actions.new_discussion": "Nova Discussão", 50 "navbar.profile_actions.new_discussion": "Nova Discussão",
44 "notification.error.default.message": "Algo deu errado!", 51 "notification.error.default.message": "Algo deu errado!",
45 "notification.error.default.title": "Oops...", 52 "notification.error.default.title": "Oops...",
  53 + "notification.info.default.message": "",
  54 + "notification.info.default.title": "Informação",
46 "notification.profile.not_found": "Página não encontrada", 55 "notification.profile.not_found": "Página não encontrada",
47 "notification.http_error.401.message": "Não autorizado", 56 "notification.http_error.401.message": "Não autorizado",
48 "notification.http_error.500.message": "Erro no servidor", 57 "notification.http_error.500.message": "Erro no servidor",