Browse Source

SPA: basket component, authentication service, basket list

pull/49/merge
Carlos Cañizares Estévez 8 years ago
parent
commit
c0c7d735e8
15 changed files with 360 additions and 40 deletions
  1. +0
    -2
      docker-compose.override.yml
  2. +2
    -0
      docker-compose.yml
  3. +4
    -3
      src/Services/Identity/eShopOnContainers.Identity/Configuration/Config.cs
  4. +2
    -1
      src/Web/WebSPA/eShopOnContainers.WebSPA/Client/modules/app.component.html
  5. +16
    -12
      src/Web/WebSPA/eShopOnContainers.WebSPA/Client/modules/app.component.ts
  6. +8
    -3
      src/Web/WebSPA/eShopOnContainers.WebSPA/Client/modules/basket/basket-status/basket-status.component.ts
  7. +8
    -4
      src/Web/WebSPA/eShopOnContainers.WebSPA/Client/modules/basket/basket.service.ts
  8. +1
    -0
      src/Web/WebSPA/eShopOnContainers.WebSPA/Client/modules/shared/components/identity/identity.html
  9. +3
    -0
      src/Web/WebSPA/eShopOnContainers.WebSPA/Client/modules/shared/components/identity/identity.scss
  10. +38
    -0
      src/Web/WebSPA/eShopOnContainers.WebSPA/Client/modules/shared/components/identity/identity.ts
  11. +3
    -0
      src/Web/WebSPA/eShopOnContainers.WebSPA/Client/modules/shared/models/identity.model.ts
  12. +19
    -10
      src/Web/WebSPA/eShopOnContainers.WebSPA/Client/modules/shared/services/data.service.ts
  13. +247
    -0
      src/Web/WebSPA/eShopOnContainers.WebSPA/Client/modules/shared/services/security.service.ts
  14. +8
    -4
      src/Web/WebSPA/eShopOnContainers.WebSPA/Client/modules/shared/shared.module.ts
  15. +1
    -1
      src/Web/WebSPA/eShopOnContainers.WebSPA/Program.cs

+ 0
- 2
docker-compose.override.yml View File

@ -19,8 +19,6 @@ services:
- BasketUrl=http://basket.api:5103 - BasketUrl=http://basket.api:5103
ports: ports:
- "5100:5100" - "5100:5100"
links:
- identity.service:10.0.75.1
webspa: webspa:
environment: environment:


+ 2
- 0
docker-compose.yml View File

@ -47,3 +47,5 @@ services:
basket.data: basket.data:
image: redis image: redis
ports:
- "6379:6379"

+ 4
- 3
src/Services/Identity/eShopOnContainers.Identity/Configuration/Config.cs View File

@ -41,8 +41,8 @@ namespace eShopOnContainers.Identity.Configuration
ClientName = "eShop SPA OpenId Client", ClientName = "eShop SPA OpenId Client",
AllowedGrantTypes = GrantTypes.Implicit, AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true, AllowAccessTokensViaBrowser = true,
RedirectUris = { $"{clientsUrl["Spa"]}/callback.html" },
PostLogoutRedirectUris = { $"{clientsUrl["Spa"]}/index.html" },
RedirectUris = { $"{clientsUrl["Spa"]}/" },
PostLogoutRedirectUris = { $"{clientsUrl["Spa"]}/" },
AllowedCorsOrigins = { $"{clientsUrl["Spa"]}" }, AllowedCorsOrigins = { $"{clientsUrl["Spa"]}" },
AllowedScopes = AllowedScopes =
{ {
@ -83,7 +83,8 @@ namespace eShopOnContainers.Identity.Configuration
{ {
$"{clientsUrl["Mvc"]}/signin-oidc", $"{clientsUrl["Mvc"]}/signin-oidc",
"http://104.40.62.65:5100/signin-oidc", "http://104.40.62.65:5100/signin-oidc",
"http://localhost:5100"
"http://localhost:5100/signin-oidc",
"http://13.88.8.119:5100/signin-oidc"
}, },
PostLogoutRedirectUris = new List<string> PostLogoutRedirectUris = new List<string>
{ {


+ 2
- 1
src/Web/WebSPA/eShopOnContainers.WebSPA/Client/modules/app.component.html View File

@ -7,7 +7,8 @@
</a> </a>
</div> </div>
<div class="col-sm-2"> <div class="col-sm-2">
<esh-basket-status></esh-basket-status>
<esh-basket-status *ngIf="Authenticated"></esh-basket-status>
<esh-identity *ngIf="!Authenticated"></esh-identity>
</div> </div>
</div> </div>
</div> </div>


+ 16
- 12
src/Web/WebSPA/eShopOnContainers.WebSPA/Client/modules/app.component.ts View File

@ -1,8 +1,10 @@
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
import { Component, ViewEncapsulation, OnInit } from '@angular/core'; import { Component, ViewEncapsulation, OnInit } from '@angular/core';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { Subscription } from 'rxjs/Subscription';
import { DataService } from './shared/services/data.service'; import { DataService } from './shared/services/data.service';
import { SecurityService } from './shared/services/security.service';
/* /*
* App Component * App Component
@ -10,22 +12,24 @@ import { DataService } from './shared/services/data.service';
*/ */
@Component({ @Component({
selector: 'appc-app',
styleUrls: ['./app.component.scss'],
templateUrl: './app.component.html'
selector: 'appc-app',
styleUrls: ['./app.component.scss'],
templateUrl: './app.component.html'
}) })
export class AppComponent implements OnInit { export class AppComponent implements OnInit {
private Authenticated: boolean = false;
subscription: Subscription;
constructor(private titleService: Title, private securityService: SecurityService) {
constructor(private titleService: Title) {
}
}
ngOnInit() {
console.log('app on init');
this.subscription = this.securityService.authenticationChallenge$.subscribe(res => this.Authenticated = res);
}
ngOnInit() {
}
public setTitle(newTitle: string) {
this.titleService.setTitle('eShopOnContainers');
}
public setTitle(newTitle: string) {
this.titleService.setTitle('eShopOnContainers');
}
} }

+ 8
- 3
src/Web/WebSPA/eShopOnContainers.WebSPA/Client/modules/basket/basket-status/basket-status.component.ts View File

@ -19,10 +19,15 @@ export class BasketStatusComponent implements OnInit {
ngOnInit() { ngOnInit() {
this.subscription = this.basketEvents.addItemToBasket$.subscribe( this.subscription = this.basketEvents.addItemToBasket$.subscribe(
item => { item => {
console.log('element received in basket');
console.log(item); console.log(item);
this.service.setBasket(item);
this.service.getBasket().subscribe(basket => {
this.badge = basket.items.length;
this.service.setBasket(item).subscribe(res => {
console.log(res);
this.service.getBasket().subscribe(basket => {
this.badge = basket.items.length;
console.log('response from basket api');
console.log(basket.items.length);
});
}); });
}); });
} }


+ 8
- 4
src/Web/WebSPA/eShopOnContainers.WebSPA/Client/modules/basket/basket.service.ts View File

@ -1,7 +1,8 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Response } from '@angular/http';
import { Response, Headers } from '@angular/http';
import { DataService } from '../shared/services/data.service'; import { DataService } from '../shared/services/data.service';
import { SecurityService } from '../shared/services/security.service';
import { IBasket } from '../shared/models/basket.model'; import { IBasket } from '../shared/models/basket.model';
import { IBasketItem } from '../shared/models/basketItem.model'; import { IBasketItem } from '../shared/models/basketItem.model';
@ -19,13 +20,16 @@ export class BasketService {
items: [] items: []
}; };
constructor(private service: DataService) {
constructor(private service: DataService, private authService: SecurityService) {
this.basket.items = []; this.basket.items = [];
} }
setBasket(item) {
setBasket(item): Observable<boolean> {
console.log('set basket');
this.basket.items.push(item); this.basket.items.push(item);
this.service.post(this.basket.buyerId, this.basket.items);
return this.service.post(this.basketUrl + '/', this.basket).map((response: Response) => {
return true;
});
} }
getBasket(): Observable<IBasket> { getBasket(): Observable<IBasket> {


+ 1
- 0
src/Web/WebSPA/eShopOnContainers.WebSPA/Client/modules/shared/components/identity/identity.html View File

@ -0,0 +1 @@
<button (click)="login()">Login</button>

+ 3
- 0
src/Web/WebSPA/eShopOnContainers.WebSPA/Client/modules/shared/components/identity/identity.scss View File

@ -0,0 +1,3 @@
.identity {
}

+ 38
- 0
src/Web/WebSPA/eShopOnContainers.WebSPA/Client/modules/shared/components/identity/identity.ts View File

@ -0,0 +1,38 @@
import { Component, OnInit, OnChanges, Output, Input, EventEmitter } from '@angular/core';
import { IIdentity } from '../../models/identity.model';
import { SecurityService } from '../../services/security.service';
@Component({
selector: 'esh-identity',
templateUrl: './identity.html',
styleUrls: ['./identity.scss']
})
export class Identity implements OnInit {
constructor(private service: SecurityService) {
}
@Output()
changed: EventEmitter<number> = new EventEmitter<number>();
@Input()
model: IIdentity;
ngOnInit() {
console.log("ngOnInit _securityService.AuthorizedCallback");
if (window.location.hash) {
this.service.AuthorizedCallback();
console.log('isAutorized?');
console.log(this.service.IsAuthorized);
}
}
login() {
this.service.Authorize();
}
logout() {
this.service.Logoff();
}
}

+ 3
- 0
src/Web/WebSPA/eShopOnContainers.WebSPA/Client/modules/shared/models/identity.model.ts View File

@ -0,0 +1,3 @@
export interface IIdentity {
}

+ 19
- 10
src/Web/WebSPA/eShopOnContainers.WebSPA/Client/modules/shared/services/data.service.ts View File

@ -8,15 +8,19 @@ import { Observer } from 'rxjs/Observer';
import 'rxjs/add/operator/map'; import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/catch';
import { SecurityService } from './security.service';
@Injectable() @Injectable()
export class DataService { export class DataService {
constructor(private http: Http) { }
constructor(private http: Http, private securityService: SecurityService) { }
get(url: string, params?: any): Observable<Response> { get(url: string, params?: any): Observable<Response> {
let options: RequestOptionsArgs = {}; let options: RequestOptionsArgs = {};
options.headers = new Headers();
this.addCors(options);
if (this.securityService) {
options.headers = new Headers();
options.headers.append("Authorization", "Bearer " + this.securityService.GetToken());
}
return this.http.get(url, options).map( return this.http.get(url, options).map(
(res: Response) => { (res: Response) => {
@ -24,13 +28,18 @@ export class DataService {
}).catch(this.handleError); }).catch(this.handleError);
} }
post(url: string, data: any, params?: any) {
return this.http.post(url, data, params);
}
post(url: string, data: any, params?: any): Observable<Response> {
let options: RequestOptionsArgs = {};
if (this.securityService) {
options.headers = new Headers();
options.headers.append("Authorization", "Bearer " + this.securityService.GetToken());
}
private addCors(options: RequestOptionsArgs): RequestOptionsArgs {
options.headers.append('Access-Control-Allow-Origin', '*');
return options;
return this.http.post(url, data, options).map(
(res: Response) => {
return res;
}).catch(this.handleError);
} }
private handleError(error: any) { private handleError(error: any) {


+ 247
- 0
src/Web/WebSPA/eShopOnContainers.WebSPA/Client/modules/shared/services/security.service.ts View File

@ -0,0 +1,247 @@
import { Injectable } from '@angular/core';
import { Http, Response, Headers } from '@angular/http';
import 'rxjs/add/operator/map';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
//import { Configuration } from '../app.constants';
import { Router } from '@angular/router';
@Injectable()
export class SecurityService {
private actionUrl: string;
private headers: Headers;
private storage: any;
private authenticationSource = new Subject<boolean>();
authenticationChallenge$ = this.authenticationSource.asObservable();
constructor(private _http: Http, private _router: Router) {
//this.actionUrl = _configuration.Server + 'api/DataEventRecords/';
this.headers = new Headers();
this.headers.append('Content-Type', 'application/json');
this.headers.append('Accept', 'application/json');
this.storage = sessionStorage; //localStorage;
if (this.retrieve("IsAuthorized") !== "") {
//this.HasAdminRole = this.retrieve("HasAdminRole");
this.IsAuthorized = this.retrieve("IsAuthorized");
}
}
public IsAuthorized: boolean;
//public HasAdminRole: boolean;
public GetToken(): any {
return this.retrieve("authorizationData");
}
public ResetAuthorizationData() {
this.store("authorizationData", "");
this.store("authorizationDataIdToken", "");
this.IsAuthorized = false;
//this.HasAdminRole = false;
this.store("HasAdminRole", false);
this.store("IsAuthorized", false);
}
public UserData: any;
public SetAuthorizationData(token: any, id_token:any) {
if (this.retrieve("authorizationData") !== "") {
this.store("authorizationData", "");
}
this.store("authorizationData", token);
this.store("authorizationDataIdToken", id_token);
this.IsAuthorized = true;
this.store("IsAuthorized", true);
//emit observable
this.authenticationSource.next(true);
this.getUserData()
.subscribe(data => this.UserData = data,
error => this.HandleError(error),
() => {
console.log(this.UserData);
});
}
public Authorize() {
this.ResetAuthorizationData();
console.log("BEGIN Authorize, no auth data");
var authorizationUrl = 'http://localhost:5105/connect/authorize';
var client_id = 'js';
var redirect_uri = 'http://localhost:5104/';
var response_type = "id_token token";
var scope = "openid profile orders basket";
var nonce = "N" + Math.random() + "" + Date.now();
var state = Date.now() + "" + Math.random();
this.store("authStateControl", state);
this.store("authNonce", nonce);
console.log("AuthorizedController created. adding myautostate: " + this.retrieve("authStateControl"));
var url =
authorizationUrl + "?" +
"response_type=" + encodeURI(response_type) + "&" +
"client_id=" + encodeURI(client_id) + "&" +
"redirect_uri=" + encodeURI(redirect_uri) + "&" +
"scope=" + encodeURI(scope) + "&" +
"nonce=" + encodeURI(nonce) + "&" +
"state=" + encodeURI(state);
window.location.href = url;
}
public AuthorizedCallback() {
console.log("BEGIN AuthorizedCallback, no auth data");
this.ResetAuthorizationData();
var hash = window.location.hash.substr(1);
var result: any = hash.split('&').reduce(function (result : any, item: string) {
var parts = item.split('=');
result[parts[0]] = parts[1];
return result;
}, {});
console.log(result);
console.log("AuthorizedCallback created, begin token validation");
var token = "";
var id_token = "";
var authResponseIsValid = false;
if (!result.error) {
if (result.state !== this.retrieve("authStateControl")) {
console.log("AuthorizedCallback incorrect state");
} else {
token = result.access_token;
id_token = result.id_token
var dataIdToken: any = this.getDataFromToken(id_token);
console.log(dataIdToken);
// validate nonce
if (dataIdToken.nonce !== this.retrieve("authNonce")) {
console.log("AuthorizedCallback incorrect nonce");
} else {
this.store("authNonce", "");
this.store("authStateControl", "");
authResponseIsValid = true;
console.log("AuthorizedCallback state and nonce validated, returning access token");
}
}
}
if (authResponseIsValid) {
this.SetAuthorizationData(token, id_token);
console.log(this.retrieve("authorizationData"));
// router navigate to DataEventRecordsList
this._router.navigate(['/dataeventrecords/list']);
}
else {
this.ResetAuthorizationData();
this._router.navigate(['/Unauthorized']);
}
}
public Logoff() {
// /connect/endsession?id_token_hint=...&post_logout_redirect_uri=https://myapp.com
console.log("BEGIN Authorize, no auth data");
var authorizationUrl = 'http://localhost:5105/connect/endsession';
console.log(this.retrieve("authorizationDataIdToken"));
var id_token_hint = this.retrieve("authorizationDataIdToken");
var post_logout_redirect_uri = 'http://localhost:5104/';
var url =
authorizationUrl + "?" +
"id_token_hint=" + encodeURI(id_token_hint) + "&" +
"post_logout_redirect_uri=" + encodeURI(post_logout_redirect_uri);
this.ResetAuthorizationData();
window.location.href = url;
}
public HandleError(error: any) {
console.log(error);
if (error.status == 403) {
this._router.navigate(['/Forbidden'])
}
else if (error.status == 401) {
this.ResetAuthorizationData();
this._router.navigate(['/Unauthorized'])
}
}
private urlBase64Decode(str: string) {
var output = str.replace('-', '+').replace('_', '/');
switch (output.length % 4) {
case 0:
break;
case 2:
output += '==';
break;
case 3:
output += '=';
break;
default:
throw 'Illegal base64url string!';
}
return window.atob(output);
}
private getDataFromToken(token: any) {
var data = {};
if (typeof token !== 'undefined') {
var encoded = token.split('.')[1];
data = JSON.parse(this.urlBase64Decode(encoded));
}
return data;
}
private retrieve(key: string): any {
var item = this.storage.getItem(key);
if (item && item !== 'undefined') {
return JSON.parse(this.storage.getItem(key));
}
return;
}
private store(key: string, value: any) {
this.storage.setItem(key, JSON.stringify(value));
}
private getUserData = (): Observable<string[]> => {
this.setHeaders();
return this._http.get('http://localhost:5105/connect/userinfo', {
headers: this.headers,
body: ''
}).map(res => res.json());
}
private setHeaders() {
this.headers = new Headers();
this.headers.append('Content-Type', 'application/json');
this.headers.append('Accept', 'application/json');
var token = this.GetToken();
if (token !== "") {
this.headers.append('Authorization', 'Bearer ' + token);
}
}
}

+ 8
- 4
src/Web/WebSPA/eShopOnContainers.WebSPA/Client/modules/shared/shared.module.ts View File

@ -17,9 +17,11 @@ import { DataService } from './services/data.service';
import { UtilityService } from './services/utility.service'; import { UtilityService } from './services/utility.service';
import { UppercasePipe } from './pipes/uppercase.pipe'; import { UppercasePipe } from './pipes/uppercase.pipe';
import { BasketWrapperService} from './services/basket.wrapper.service'; import { BasketWrapperService} from './services/basket.wrapper.service';
import { SecurityService } from './services/security.service';
//Components: //Components:
import {Pager } from './components/pager/pager'; import {Pager } from './components/pager/pager';
import {Identity } from './components/identity/identity';
@NgModule({ @NgModule({
imports: [ imports: [
@ -39,7 +41,8 @@ import {Pager } from './components/pager/pager';
ErrorSummaryComponent, ErrorSummaryComponent,
PageHeadingComponent, PageHeadingComponent,
UppercasePipe, UppercasePipe,
Pager
Pager,
Identity
], ],
exports: [ exports: [
// Modules // Modules
@ -57,9 +60,9 @@ import {Pager } from './components/pager/pager';
//HeaderComponent, //HeaderComponent,
PageHeadingComponent, PageHeadingComponent,
UppercasePipe, UppercasePipe,
Pager
Pager,
Identity
] ]
}) })
export class SharedModule { export class SharedModule {
static forRoot(): ModuleWithProviders { static forRoot(): ModuleWithProviders {
@ -70,7 +73,8 @@ export class SharedModule {
DataService, DataService,
FormControlService, FormControlService,
UtilityService, UtilityService,
BasketWrapperService
BasketWrapperService,
SecurityService
] ]
}; };
} }


+ 1
- 1
src/Web/WebSPA/eShopOnContainers.WebSPA/Program.cs View File

@ -17,8 +17,8 @@ namespace eShopConContainers.WebSPA
.UseKestrel() .UseKestrel()
.UseConfiguration(config) .UseConfiguration(config)
.UseContentRoot(Directory.GetCurrentDirectory()) .UseContentRoot(Directory.GetCurrentDirectory())
.UseUrls("http://localhost:5104/")
.UseIISIntegration() .UseIISIntegration()
//.UseUrls("http://localhost:5104/")
.UseStartup<Startup>() .UseStartup<Startup>()
.Build(); .Build();


Loading…
Cancel
Save