Browse Source

24-322 Switching to NgRx for authentication

ngrx
Nils Dittberner 8 years ago
parent
commit
bae3653b7a
13 changed files with 147 additions and 54 deletions
  1. +2
    -2
      src/app/app.module.ts
  2. +9
    -3
      src/app/auth/auth-guard.service.ts
  3. +24
    -18
      src/app/auth/auth.service.ts
  4. +26
    -0
      src/app/auth/ngrx/auth.actions.ts
  5. +35
    -0
      src/app/auth/ngrx/auth.reducers.ts
  6. +3
    -3
      src/app/core/header/header.component.html
  7. +15
    -7
      src/app/core/header/header.component.ts
  8. +14
    -0
      src/app/ngrx/app.reducers.ts
  9. +2
    -2
      src/app/recipes/recipe-detail/recipe-detail.component.ts
  10. +13
    -6
      src/app/shared/auth.interceptor.ts
  11. +0
    -9
      src/app/shopping-list/ngrx/shopping-list.reducers.ts
  12. +2
    -2
      src/app/shopping-list/shopping-edit/shopping-edit.component.ts
  13. +2
    -2
      src/app/shopping-list/shopping-list.component.ts

+ 2
- 2
src/app/app.module.ts View File

@@ -9,7 +9,7 @@ import { SharedModule } from './shared/shared.module';
import { ShoppingListModule } from './shopping-list/shopping-list.module'; import { ShoppingListModule } from './shopping-list/shopping-list.module';
import { AuthModule } from './auth/auth.module'; import { AuthModule } from './auth/auth.module';
import { CoreModule } from './core/core.module'; import { CoreModule } from './core/core.module';
import { shoppingListReducer } from './shopping-list/ngrx/shopping-list.reducers';
import { appReducers } from './ngrx/app.reducers';


@NgModule({ @NgModule({
declarations: [ declarations: [
@@ -23,7 +23,7 @@ import { shoppingListReducer } from './shopping-list/ngrx/shopping-list.reducers
ShoppingListModule, ShoppingListModule,
AuthModule, AuthModule,
CoreModule, CoreModule,
StoreModule.forRoot({shoppingList: shoppingListReducer})
StoreModule.forRoot(appReducers)
], ],
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })


+ 9
- 3
src/app/auth/auth-guard.service.ts View File

@@ -1,14 +1,20 @@
import { CanActivate } from "@angular/router"; import { CanActivate } from "@angular/router";
import { ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router/src/router_state"; import { ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router/src/router_state";
import { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import { AuthService } from "./auth.service";
import { Store } from "@ngrx/store";
import "rxjs/add/operator/map";

import * as fromApp from '../ngrx/app.reducers';
import * as fromAuth from './ngrx/auth.reducers';


@Injectable() @Injectable()
export class AuthGuard implements CanActivate { export class AuthGuard implements CanActivate {
constructor(private authService: AuthService) {}
constructor(private store: Store<fromApp.AppState>) {}


canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
return this.authService.isAuthenticated();
return this.store.select('auth').map((authState: fromAuth.State) => {
return authState.authenticated;
});
} }
} }

+ 24
- 18
src/app/auth/auth.service.ts View File

@@ -1,15 +1,30 @@
import * as firebase from 'firebase'; import * as firebase from 'firebase';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';

import * as fromApp from '../ngrx/app.reducers';
import * as AuthActions from './ngrx/auth.actions';


@Injectable() @Injectable()
export class AuthService { export class AuthService {
token: string;

constructor(private router: Router) {}
constructor(private router: Router, private store: Store<fromApp.AppState>) {}


signupUser(email: string, password: string) { signupUser(email: string, password: string) {
firebase.auth().createUserWithEmailAndPassword(email, password).catch(
firebase.auth().createUserWithEmailAndPassword(email, password)
.then(
user => {
this.store.dispatch(new AuthActions.Signup());
firebase.auth().currentUser.getIdToken()
.then(
(token: string) => {
this.store.dispatch(new AuthActions.SetToken(token));
}
);

}
)
.catch(
error => console.log(error) error => console.log(error)
); );
} }
@@ -18,10 +33,13 @@ export class AuthService {
firebase.auth().signInWithEmailAndPassword(email, password) firebase.auth().signInWithEmailAndPassword(email, password)
.then( .then(
response => { response => {
this.store.dispatch(new AuthActions.Signin());
this.router.navigate(['/']); this.router.navigate(['/']);
firebase.auth().currentUser.getIdToken() firebase.auth().currentUser.getIdToken()
.then( .then(
(token: string) => this.token = token
(token: string) => {
this.store.dispatch(new AuthActions.SetToken(token));
}
); );
} }
) )
@@ -32,19 +50,7 @@ export class AuthService {


logout() { logout() {
firebase.auth().signOut(); firebase.auth().signOut();
this.token = null;
this.store.dispatch(new AuthActions.Logout());
this.router.navigate(['/']); this.router.navigate(['/']);
} }

getToken() {
firebase.auth().currentUser.getIdToken()
.then(
(token: string) => this.token = token
);
return this.token;
}

isAuthenticated() {
return this.token != null;
}
} }

+ 26
- 0
src/app/auth/ngrx/auth.actions.ts View File

@@ -0,0 +1,26 @@
import { Action } from '@ngrx/store';

export const SIGNUP = 'SIGNUP';
export const SIGNIN = 'SIGNIN';
export const LOGOUT = 'LOGOUT';
export const SET_TOKEN = 'SET_TOKEN';

export class Signup implements Action {
readonly type = SIGNUP;
}

export class Signin implements Action {
readonly type = SIGNIN;
}

export class Logout implements Action {
readonly type = LOGOUT;
}

export class SetToken implements Action {
readonly type = SET_TOKEN;

constructor(public payload: string) {}
}

export type AuthActions = Signup | Signin | Logout | SetToken;

+ 35
- 0
src/app/auth/ngrx/auth.reducers.ts View File

@@ -0,0 +1,35 @@
import * as AuthActions from './auth.actions';

export interface State {
token: string;
authenticated: boolean;
}

const initialState: State = {
token: null,
authenticated: false
};

export function authReducers(state = initialState, action: AuthActions.AuthActions) {
switch (action.type) {
case AuthActions.SIGNUP:
case AuthActions.SIGNIN:
return {
...state,
authenticated: true
};
case AuthActions.LOGOUT:
return {
...state,
token: null,
authenticated: false
};
case AuthActions.SET_TOKEN:
return {
...state,
token: action.payload
};
default:
return state;
}
}

+ 3
- 3
src/app/core/header/header.component.html View File

@@ -10,12 +10,12 @@
<li routerLinkActive="active"><a routerLink="/shopping-list">Shopping List</a></li> <li routerLinkActive="active"><a routerLink="/shopping-list">Shopping List</a></li>
</ul> </ul>
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
<ng-template [ngIf]="!isAuthenticated()">
<ng-template [ngIf]="!(authState | async).authenticated">
<li><a routerLink="/signup">Register</a></li> <li><a routerLink="/signup">Register</a></li>
<li><a routerLink="/signin">Login</a></li> <li><a routerLink="/signin">Login</a></li>
</ng-template> </ng-template>
<li><a style="cursor: pointer;" (click)="onLogout()" *ngIf="isAuthenticated()">Logout</a></li>
<li class="dropdown" appDropdown *ngIf="isAuthenticated()">
<li><a style="cursor: pointer;" (click)="onLogout()" *ngIf="(authState | async).authenticated">Logout</a></li>
<li class="dropdown" appDropdown *ngIf="(authState | async).authenticated">
<a style="cursor: pointer;" class="dropdown-toggle" role="button">Manage <span class="caret"></span></a> <a style="cursor: pointer;" class="dropdown-toggle" role="button">Manage <span class="caret"></span></a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a style="cursor: pointer;" (click)="onSaveData()">Save</a></li> <li><a style="cursor: pointer;" (click)="onSaveData()">Save</a></li>


+ 15
- 7
src/app/core/header/header.component.ts View File

@@ -1,14 +1,26 @@
import { Component } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import { Store } from "@ngrx/store";
import { Observable } from "rxjs/Observable";

import { DataStorageService } from "../../shared/data-storage.service"; import { DataStorageService } from "../../shared/data-storage.service";
import { AuthService } from "../../auth/auth.service"; import { AuthService } from "../../auth/auth.service";
import * as fromApp from '../../ngrx/app.reducers';
import * as fromAuth from '../../auth/ngrx/auth.reducers';


@Component({ @Component({
selector: 'app-header', selector: 'app-header',
templateUrl: './header.component.html' templateUrl: './header.component.html'
}) })
export class HeaderComponent {
export class HeaderComponent implements OnInit {
authState: Observable<fromAuth.State>;

constructor(private dataStorageService: DataStorageService,
private authService: AuthService,
private store: Store<fromApp.AppState>) {}


constructor(private dataStorageService: DataStorageService, private authService: AuthService) {}
ngOnInit() {
this.authState = this.store.select('auth')
}


onSaveData() { onSaveData() {
this.dataStorageService.storeRecipes().subscribe( this.dataStorageService.storeRecipes().subscribe(
@@ -25,8 +37,4 @@ export class HeaderComponent {
onLogout() { onLogout() {
this.authService.logout(); this.authService.logout();
} }

isAuthenticated() {
return this.authService.isAuthenticated();
}
} }

+ 14
- 0
src/app/ngrx/app.reducers.ts View File

@@ -0,0 +1,14 @@
import { ActionReducerMap } from '@ngrx/store/src/models';

import * as fromShoppingList from '../shopping-list/ngrx/shopping-list.reducers'
import * as fromAuth from '../auth/ngrx/auth.reducers'

export interface AppState {
shoppingList: fromShoppingList.State,
auth: fromAuth.State
}

export const appReducers: ActionReducerMap<AppState> = {
shoppingList: fromShoppingList.shoppingListReducer,
auth: fromAuth.authReducers
};

+ 2
- 2
src/app/recipes/recipe-detail/recipe-detail.component.ts View File

@@ -5,7 +5,7 @@ import { Store } from '@ngrx/store';
import { Recipe } from '../recipe.model'; import { Recipe } from '../recipe.model';
import { RecipeService } from '../recipe.service'; import { RecipeService } from '../recipe.service';
import * as ShoppingListActions from '../../shopping-list/ngrx/shopping-list.actions'; import * as ShoppingListActions from '../../shopping-list/ngrx/shopping-list.actions';
import * as fromShoppingList from '../../shopping-list/ngrx/shopping-list.reducers';
import * as fromApp from '../../ngrx/app.reducers';


@Component({ @Component({
selector: 'app-recipe-detail', selector: 'app-recipe-detail',
@@ -19,7 +19,7 @@ export class RecipeDetailComponent implements OnInit {
constructor(private recipeService: RecipeService, constructor(private recipeService: RecipeService,
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router, private router: Router,
private store: Store<fromShoppingList.AppState>) { }
private store: Store<fromApp.AppState>) { }


ngOnInit() { ngOnInit() {
this.route.params.subscribe( this.route.params.subscribe(


+ 13
- 6
src/app/shared/auth.interceptor.ts View File

@@ -1,16 +1,23 @@
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http' import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http'
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { AuthService } from '../auth/auth.service';
import { Store } from "@ngrx/store";
import "rxjs/add/operator/switchMap";
import "rxjs/add/operator/take";

import * as fromApp from '../ngrx/app.reducers';
import * as fromAuth from '../auth/ngrx/auth.reducers';


@Injectable() @Injectable()
export class AuthInterceptor implements HttpInterceptor { export class AuthInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) {}
constructor(private store: Store<fromApp.AppState>) {}


intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const copiedReq = req.clone({
params: req.params.set('auth', this.authService.getToken())
});
return next.handle(copiedReq);
return this.store.select('auth')
.take(1)
.switchMap((authState: fromAuth.State) => {
const copiedReq = req.clone({params: req.params.set('auth', authState.token)});
return next.handle(copiedReq);
});
} }
} }

+ 0
- 9
src/app/shopping-list/ngrx/shopping-list.reducers.ts View File

@@ -2,21 +2,12 @@ import * as ShoppingListActions from './shopping-list.actions';


import { Ingredient } from '../../shared/ingredient.model'; import { Ingredient } from '../../shared/ingredient.model';


export interface AppState {
shoppingList: State
}

export interface State { export interface State {
ingredients: Ingredient[]; ingredients: Ingredient[];
editedIngredient: Ingredient; editedIngredient: Ingredient;
editedIngredientIndex: number; editedIngredientIndex: number;
} }


export const ADD_INGREDIENT = 'ADD_INGREDIENT';
export const ADD_INGREDIENTS = 'ADD_INGREDIENTS';
export const UPDATE_INGREDIENT = 'UPDATE_INGREDIENT';
export const DELETE_INGREDIENT = 'DELETE_INGREDIENT';

const initialState: State = { const initialState: State = {
ingredients: [ ingredients: [
new Ingredient('Banana', 10) new Ingredient('Banana', 10)


+ 2
- 2
src/app/shopping-list/shopping-edit/shopping-edit.component.ts View File

@@ -5,7 +5,7 @@ import { Store } from '@ngrx/store';


import { Ingredient } from '../../shared/ingredient.model'; import { Ingredient } from '../../shared/ingredient.model';
import * as ShoppingListActions from '../ngrx/shopping-list.actions'; import * as ShoppingListActions from '../ngrx/shopping-list.actions';
import * as fromShoppingList from '../ngrx/shopping-list.reducers';
import * as fromApp from '../../ngrx/app.reducers'


@Component({ @Component({
selector: 'app-shopping-edit', selector: 'app-shopping-edit',
@@ -18,7 +18,7 @@ export class ShoppingEditComponent implements OnInit, OnDestroy {
editMode = false; editMode = false;
editedItem: Ingredient; editedItem: Ingredient;


constructor(private store: Store<fromShoppingList.AppState>) { }
constructor(private store: Store<fromApp.AppState>) { }


ngOnInit() { ngOnInit() {
this.subscription = this.store.select('shoppingList').subscribe( this.subscription = this.store.select('shoppingList').subscribe(


+ 2
- 2
src/app/shopping-list/shopping-list.component.ts View File

@@ -4,7 +4,7 @@ import { Observable } from 'rxjs/Observable';


import { Ingredient } from '../shared/ingredient.model'; import { Ingredient } from '../shared/ingredient.model';
import * as ShoppingListActions from './ngrx/shopping-list.actions'; import * as ShoppingListActions from './ngrx/shopping-list.actions';
import * as fromShoppingList from './ngrx/shopping-list.reducers';
import * as fromApp from '../ngrx/app.reducers'


@Component({ @Component({
selector: 'app-shopping-list', selector: 'app-shopping-list',
@@ -14,7 +14,7 @@ import * as fromShoppingList from './ngrx/shopping-list.reducers';
export class ShoppingListComponent implements OnInit { export class ShoppingListComponent implements OnInit {
shoppingListState: Observable<{ingredients: Ingredient[]}>; shoppingListState: Observable<{ingredients: Ingredient[]}>;


constructor(private store: Store<fromShoppingList.AppState>) { }
constructor(private store: Store<fromApp.AppState>) { }


ngOnInit() { ngOnInit() {
this.shoppingListState = this.store.select('shoppingList'); this.shoppingListState = this.store.select('shoppingList');


Loading…
Cancel
Save